"""
TRF (Tournament Report File) generator for bbpPairings.

Generates TRF-2026 format from parsed chess-results.com data.
Format specification: FIDE C04 Annex 2 (TRF16).
bbpPairings extensions: XXC rank, XXR <round>, 152 W/B.
"""

import io
from typing import Optional


def _pad_right(text: str, width: int) -> str:
    """Pad text to exactly `width` chars, left-aligned."""
    if len(text) > width:
        text = text[:width]
    return text.ljust(width)


def _pad_left(text: str, width: int) -> str:
    """Pad text to exactly `width` chars, right-aligned."""
    if len(text) > width:
        text = text[-width:]
    return text.rjust(width)


def _fixed_field(line: list, position: int, value: str):
    """Place `value` at 1-indexed `position` in the mutable `line` list."""
    pos = position - 1
    # Ensure line is long enough
    while len(line) < pos + len(value):
        line.append(' ')
    for i, ch in enumerate(value):
        line[pos + i] = ch


RESULT_CODES = {
    1.0: '1',
    0.5: 'D',   # Draw (TRF standard; '=' is not accepted by bbpPairings)
    0.0: '0',
}

BYE_RESULT_CODES = {
    1.0: '+',   # forfeit win / full-point bye
    0.5: 'H',   # half-point bye  
    0.0: '-',   # forfeit loss / zero-point bye
}


def generate_trf(
    tournament: dict,
    next_round: int,
    name: str = "Chess Tournament",
    use_rank: bool = True,
    initial_color_white: bool = True,
) -> str:
    """Generate TRF file content for bbpPairings.

    Args:
        tournament: Parsed tournament data from parser.fetch_tournament().
        next_round: Round number to pair (e.g., 4 if rounds 1-3 are complete).
        name: Tournament name.
        use_rank: If True, use XXC rank (rank from standings for ordering).
        initial_color_white: If True, top player gets white in round 1.

    Returns:
        TRF file content as string.
    """
    buf = io.StringIO()
    num_rounds = tournament.get('num_rounds', next_round)

    # Tournament header
    buf.write(f"012 {name}\n")

    # Configuration lines
    config_parts = []
    if use_rank:
        config_parts.append("rank")
    if initial_color_white:
        config_parts.append("white1")
    else:
        config_parts.append("black1")
    if config_parts:
        buf.write(f"XXC {' '.join(config_parts)}\n")

    # Players — sort by actual points (desc) then starting_sno for stable order.
    # The order in the TRF file matters: bbpPairings uses it as fallback ordering
    # within score brackets when ratings are unavailable.
    standings = sorted(
        tournament['standings'],
        key=lambda s: (-s['points'], s.get('starting_sno', s['rank']))
    )
    for s in standings:
        # Build a line as a list of chars (mutable)
        line = [' '] * 200

        # Position 1-3: Data identification (001)
        _fixed_field(line, 1, '001')

        # Position 5-8: Starting rank number (right-aligned, 4 chars)
        sno = s.get('starting_sno', s['rank'])
        _fixed_field(line, 5, _pad_left(str(sno), 4))

        # Position 10: Sex (m/w)
        # Not available from chess-results, leave empty

        # Position 11-13: Title
        # Not available from chess-results, leave empty

        # Position 15-47: Name (33 chars)
        # chess-results gives "Last, First" or "Last First" format
        player_name = s['name']
        if ',' in player_name:
            # Already "Last, First"
            pass
        else:
            # Try to split: last word = last name, rest = first name
            parts = player_name.split()
            if len(parts) >= 2:
                player_name = f"{parts[0]}, {' '.join(parts[1:])}"
        _fixed_field(line, 15, _pad_right(player_name, 33))

        # Position 49-52: Rating (4 chars, right-aligned)
        rating = s.get('rating', 0)
        if rating:
            _fixed_field(line, 49, _pad_left(str(rating), 4))

        # Position 54-56: Federation (3 chars)
        fed = s.get('fed', '')
        if fed:
            _fixed_field(line, 54, _pad_right(fed[:3], 3))

        # Position 81-84: Points (4 chars, right-aligned)
        points = s['points']
        pts_str = f"{points:.1f}"
        _fixed_field(line, 81, _pad_left(pts_str, 4))

        # Position 86-89: Rank (4 chars, right-aligned)
        # IMPORTANT: this MUST be the initial/stable rank (starting SNo),
        # NOT the current standings position. bbpPairings uses this
        # for ordering within score brackets when XXC rank is set.
        _fixed_field(line, 86, _pad_left(str(sno), 4))

        # Round results (starting at position 92, each round = 10 chars)
        results = s.get('results', [])
        sno = s.get('starting_sno', s['rank'])
        for ri, r in enumerate(results):
            base_pos = 92 + ri * 10
            opp = r.get('opponent', r.get('opponent_sno', 0))
            color = r.get('color', '-')
            score = r.get('score', 0)

            # Skip self-matches (data artifacts on chess-results.com)
            if opp == sno:
                opp = 0

            if opp > 0:
                # Regular game
                # Position 92+ri*10 to 92+ri*10+3: opponent SNo (4 chars)
                _fixed_field(line, base_pos, _pad_left(str(opp), 4))
                # Position 92+ri*10+5: color (w/b)
                _fixed_field(line, base_pos + 5, color)
                # Position 92+ri*10+7: result code
                result_char = RESULT_CODES.get(score, 'Z')
                _fixed_field(line, base_pos + 7, result_char)
            else:
                # Bye / forfeit / unpaired
                _fixed_field(line, base_pos, '0000')
                _fixed_field(line, base_pos + 5, '-')
                result_char = BYE_RESULT_CODES.get(score, '-')
                _fixed_field(line, base_pos + 7, result_char)

        # Trim trailing spaces
        line_str = ''.join(line).rstrip()
        if line_str.strip():
            buf.write(line_str + '\n')

    # Total rounds (XXR = total number of rounds in tournament)
    buf.write(f"XXR {num_rounds}\n")

    return buf.getvalue()
