from __future__ import annotations

import json
import tempfile
import unittest
from datetime import date, datetime
from pathlib import Path

from hockey_data.config import HockeySettingsStore
from hockey_data.match_details_parser import (
    parse_match_details_json,
    parse_players_xml,
    parse_referees_xml,
)
from hockey_data.match_parser import ParsedGame, parse_date_value
from hockey_data.models import Game, Tournament
from hockey_data.service import HockeyDataService
from support import LocalTestDatabase


PLAYERS_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<PlayerStatsList tournamentId="1369">
  <PlayerStats id="101" pos="\xd0\xbd" name="\xd0\x98\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xbe\xd0\xb2 \xd0\x98\xd0\xb2\xd0\xb0\xd0\xbd" nameen="Ivanov Ivan"
    lastname="\xd0\x98\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xbe\xd0\xb2" firstname="\xd0\x98\xd0\xb2\xd0\xb0\xd0\xbd" lastnameen="Ivanov" firstnameen="Ivan" birthdate="2000-01-02"
    gp="63" g="11" a="14" pts="25" pm="5" pim="24" sog="111" sog_pct="9.9" hits="22" bls="50" fo="100" fow="55" fo_pct="55.0" toi_avg="14:54" />
  <PlayerStats id="202" pos="\xd0\xb2" name="\xd0\x9f\xd0\xb5\xd1\x82\xd1\x80\xd0\xbe\xd0\xb2 \xd0\x9f\xd1\x91\xd1\x82\xd1\x80" nameen="Petrov Pyotr"
    lastname="\xd0\x9f\xd0\xb5\xd1\x82\xd1\x80\xd0\xbe\xd0\xb2" firstname="\xd0\x9f\xd1\x91\xd1\x82\xd1\x80" lastnameen="Petrov" firstnameen="Pyotr"
    gp="40" w="25" l="10" n="0" sop="5" sog="1000" ga="80" sv="920" sv_pct="92.0" gaa="2.00" so="5" toi="2400:00" />
</PlayerStatsList>"""

REFEREES_XML = b"""<?xml version="1.0" encoding="UTF-8"?>
<Referees tournamentId="1369">
  <Referee id="301" name="\xd0\xa1\xd0\xb8\xd0\xb4\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb2 \xd0\x90\xd0\xbb\xd0\xb5\xd0\xba\xd1\x81\xd0\xb5\xd0\xb9" nameen="Sidorov Alexei"
    lastname="\xd0\xa1\xd0\xb8\xd0\xb4\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb2" firstname="\xd0\x90\xd0\xbb\xd0\xb5\xd0\xba\xd1\x81\xd0\xb5\xd0\xb9" lastnameen="Sidorov" firstnameen="Alexei"
    pos="m" jn="18" isactive="Y" />
  <Referee id="302" name="\xd0\xa1\xd0\xbc\xd0\xb8\xd1\x80\xd0\xbd\xd0\xbe\xd0\xb2 \xd0\x9e\xd0\xbb\xd0\xb5\xd0\xb3" nameen="Smirnov Oleg" pos="l" jn="22" isactive="Y" />
</Referees>"""

MATCH_RU = """{
  "game": {
    "idschedule": 900001,
    "teamA": "Команда А",
    "teamB": "Команда Б",
    "idclubA": 1,
    "idteamA": 11,
    "idclubB": 2,
    "idteamB": 22,
    "mref1": "Сидоров Алексей",
    "mref1_id": 301,
    "mref1_num": "18",
    "lref1": "Смирнов Олег",
    "lref1_id": 302,
    "lref1_num": "22",
    "status": "матч не начался"
  },
  "teams": {
    "A": {"coach_fullname": "Тренер Первый", "shots": 31, "goals": 3, "fo": 50, "fow": 28, "fow_pct": "56.00", "hits": 14, "bls": 9, "pim": 6, "toa": "14:20"},
    "B": {"coach_fullname": "Тренер Второй", "shots": 27, "goals": 2, "fo": 50, "fow": 22, "fow_pct": "44.00", "hits": 18, "bls": 12, "pim": 8, "toa": "11:05"}
  },
  "teams_periods": {
    "A": {"1": {"shots": 10, "goals": 1, "fo": 18, "fow": 11, "pim": 2}, "2": {"shots": 12, "goals": 2, "fo": 17, "fow": 9, "pim": 4}},
    "B": {"1": {"shots": 8, "goals": 0, "fo": 18, "fow": 7, "pim": 4}, "2": {"shots": 11, "goals": 1, "fo": 17, "fow": 8, "pim": 2}}
  },
  "players": {
    "A": {"17": {"id": "101", "name": "Иванов Иван", "ps": "н", "pig": 1, "line": "1", "ca": "к", "goals": 1, "assists": 2, "pts": 3, "shots": 4, "hits": 2, "bls": 1, "fo": 10, "fow": 6, "toi": "17:20", "tie": "13:00", "tipp": "03:10", "tish": "01:10", "si": 21}},
    "B": {"30": {"id": "202", "name": "Петров Пётр", "ps": "в", "pig": 1, "line": "", "sog": 31, "sv": 29, "ga": 2, "sv_pct": 93.55, "toi": "59:40"}}
  }
}"""

MATCH_EN = """{
  "game": {
    "idschedule": 900001,
    "teamA": "Team A",
    "teamB": "Team B",
    "idclubA": 1,
    "idteamA": 11,
    "idclubB": 2,
    "idteamB": 22,
    "mref1": "Sidorov Alexei",
    "mref1_id": 301,
    "mref1_num": "18",
    "lref1": "Smirnov Oleg",
    "lref1_id": 302,
    "lref1_num": "22",
    "status": "scheduled"
  },
  "teams": {
    "A": {"coach_fullname": "Coach One", "shots": 31, "goals": 3, "fo": 50, "fow": 28, "fow_pct": "56.00", "hits": 14, "bls": 9, "pim": 6, "toa": "14:20"},
    "B": {"coach_fullname": "Coach Two", "shots": 27, "goals": 2, "fo": 50, "fow": 22, "fow_pct": "44.00", "hits": 18, "bls": 12, "pim": 8, "toa": "11:05"}
  },
  "teams_periods": {
    "A": {"1": {"shots": 10, "goals": 1, "fo": 18, "fow": 11, "pim": 2}, "2": {"shots": 12, "goals": 2, "fo": 17, "fow": 9, "pim": 4}},
    "B": {"1": {"shots": 8, "goals": 0, "fo": 18, "fow": 7, "pim": 4}, "2": {"shots": 11, "goals": 1, "fo": 17, "fow": 8, "pim": 2}}
  },
  "players": {
    "A": {"17": {"id": "101", "name": "Ivanov Ivan", "ps": "f", "pig": 1, "line": "1", "ca": "c", "goals": 1, "assists": 2, "pts": 3, "shots": 4, "hits": 2, "bls": 1, "fo": 10, "fow": 6, "toi": "17:20", "tie": "13:00", "tipp": "03:10", "tish": "01:10", "si": 21}},
    "B": {"30": {"id": "202", "name": "Petrov Pyotr", "ps": "g", "pig": 1, "line": "", "sog": 31, "sv": 29, "ga": 2, "sv_pct": 93.55, "toi": "59:40"}}
  }
}"""


class MatchDetailsParserTests(unittest.TestCase):

    def test_parses_localized_match_datetime_into_real_game_date(self) -> None:
        self.assertEqual(
            parse_date_value("16 августа 2026, Вс 13:00:00"),
            date(2026, 8, 16),
        )
        self.assertEqual(
            parse_date_value("August 16, 2026, Sun 13:00:00"),
            date(2026, 8, 16),
        )

    def test_details_date_is_used_only_as_schedule_fallback(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            base_dir = Path(temp_dir)
            database = LocalTestDatabase(base_dir / "test.sqlite3")
            database.create_all()
            settings = HockeySettingsStore(base_dir / "settings")
            service = HockeyDataService(
                base_dir=base_dir,
                database=database,
                settings=settings,
            )
            with database.session() as session:
                session.add(Tournament(external_id="1369"))
                session.add(
                    Game(
                        external_id="900001",
                        tournament_external_id="1369",
                    )
                )

            match_ru = MATCH_RU.replace(
                '"status": "матч не начался"',
                '"status": "матч не начался", "date": "16 августа 2026, Вс 13:00:00"',
            )
            details = parse_match_details_json(match_ru, MATCH_EN)
            with database.session() as session:
                game = session.query(Game).filter_by(external_id="900001").one()
                service._apply_details_game_fields(game, details, now=datetime.utcnow())

            with database.session() as session:
                game = session.query(Game).filter_by(external_id="900001").one()
                self.assertEqual(game.game_date, date(2026, 8, 16))
                self.assertEqual(game.start_time, "13:00")
                self.assertEqual(game.start_datetime_raw, "16 августа 2026, Вс 13:00:00")

                # A later schedule refresh with an empty date must not erase
                # the date recovered from the selected game's detail card.
                service._upsert_game(
                    session,
                    ParsedGame(
                        external_id="900001",
                        tournament_external_id="1369",
                        game_date=None,
                        start_time="",
                        start_datetime_raw="",
                    ),
                    endpoint="schedule-test",
                    now=datetime.utcnow(),
                )

            with database.session() as session:
                game = session.query(Game).filter_by(external_id="900001").one()
                self.assertEqual(game.game_date, date(2026, 8, 16))
                self.assertEqual(game.start_time, "13:00")

                # If the schedule does have a real date, it stays primary.
                game.game_date = date(2026, 8, 17)
                session.flush()
                service._apply_details_game_fields(game, details, now=datetime.utcnow())
                self.assertEqual(game.game_date, date(2026, 8, 17))
            database.engine.dispose()


    def test_exact_khl_game_date_and_starttime_are_saved(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            base_dir = Path(temp_dir)
            database = LocalTestDatabase(base_dir / "test.sqlite3")
            database.create_all()
            settings = HockeySettingsStore(base_dir / "settings")
            service = HockeyDataService(
                base_dir=base_dir,
                database=database,
                settings=settings,
            )
            payload = json.dumps({
                "game": {
                    "idschedule": 902733,
                    "number": 5,
                    "date": "16 августа 2026, Вс",
                    "starttime": "13:00:00",
                    "teamA": "Спартак",
                    "teamB": "Северсталь",
                    "idclubA": 7,
                    "idteamA": 15655,
                    "idclubB": 56,
                    "idteamB": 15652,
                    "score": "6:4",
                    "status": "матч завершен",
                    "statusvalue": 90,
                    "place": "ЛД «Легенды хоккея - Сокольники»"
                }
            }, ensure_ascii=False)
            details = parse_match_details_json(payload, expected_game_id="902733")
            with database.session() as session:
                game = Game(external_id="902733", tournament_external_id="1369")
                session.add(game)
                service._apply_details_game_fields(game, details, now=datetime.utcnow())
            with database.session() as session:
                game = session.query(Game).filter_by(external_id="902733").one()
                self.assertEqual(game.game_date, date(2026, 8, 16))
                self.assertEqual(game.start_time, "13:00")
                self.assertEqual(game.start_datetime_raw, "16 августа 2026, Вс 13:00:00")

    def test_schedule_raw_localized_datetime_recovers_game_date(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            base_dir = Path(temp_dir)
            database = LocalTestDatabase(base_dir / "test.sqlite3")
            database.create_all()
            settings = HockeySettingsStore(base_dir / "settings")
            service = HockeyDataService(
                base_dir=base_dir,
                database=database,
                settings=settings,
            )
            with database.session() as session:
                service._upsert_game(
                    session,
                    ParsedGame(
                        external_id="902733",
                        tournament_external_id="1369",
                        game_date=None,
                        start_time="",
                        start_datetime_raw="16 августа 2026, Вс 13:00:00",
                    ),
                    endpoint="raw-date-test",
                    now=datetime.utcnow(),
                )
            with database.session() as session:
                game = session.query(Game).filter_by(external_id="902733").one()
                self.assertEqual(game.game_date, date(2026, 8, 16))
                self.assertEqual(game.start_time, "13:00")

    def test_backfills_existing_null_game_date_from_raw_datetime(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            base_dir = Path(temp_dir)
            database = LocalTestDatabase(base_dir / "test.sqlite3")
            database.create_all()
            settings = HockeySettingsStore(base_dir / "settings")
            service = HockeyDataService(
                base_dir=base_dir,
                database=database,
                settings=settings,
            )
            with database.session() as session:
                session.add(
                    Game(
                        external_id="902733",
                        tournament_external_id="1369",
                        game_date=None,
                        start_time="13:00",
                        start_datetime_raw="16 августа 2026, Вс 13:00:00",
                    )
                )
            with database.session() as session:
                repaired = service._backfill_missing_game_dates(session)
                self.assertEqual(repaired, 1)
            with database.session() as session:
                game = session.query(Game).filter_by(external_id="902733").one()
                self.assertEqual(game.game_date, date(2026, 8, 16))
                self.assertEqual(game.start_time, "13:00")

    def test_parses_bilingual_rosters_and_officials(self) -> None:
        details = parse_match_details_json(
            MATCH_RU,
            MATCH_EN,
            expected_game_id="900001",
        )
        self.assertEqual(details.game_external_id, "900001")
        self.assertEqual(len(details.rosters), 2)
        self.assertEqual(details.rosters[0].role, "forward")
        self.assertEqual(details.rosters[0].captain_role, "captain")
        self.assertEqual(details.rosters[1].role, "goalkeeper")
        self.assertEqual(len(details.officials), 2)
        self.assertEqual(details.officials[0].role, "head")

    def test_stores_and_serializes_match_details(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            base_dir = Path(temp_dir)
            database = LocalTestDatabase(base_dir / "test.sqlite3")
            database.create_all()
            settings = HockeySettingsStore(base_dir / "settings")
            service = HockeyDataService(
                base_dir=base_dir,
                database=database,
                settings=settings,
            )
            with database.session() as session:
                session.add(Tournament(external_id="1369"))
            with database.session() as session:
                session.add(
                    Game(
                        external_id="900001",
                        tournament_external_id="1369",
                        home_team_external_id="1",
                        away_team_external_id="2",
                        home_team_name_ru="Команда А",
                        home_team_name_en="Team A",
                        away_team_name_ru="Команда Б",
                        away_team_name_en="Team B",
                    )
                )

            details = parse_match_details_json(MATCH_RU, MATCH_EN)
            players = parse_players_xml(PLAYERS_XML)
            referees = parse_referees_xml(REFEREES_XML)
            with database.session() as session:
                game = session.query(Game).filter_by(external_id="900001").one()
                service._store_match_details(
                    session,
                    game=game,
                    details=details,
                    player_profiles=players,
                    referee_profiles=referees,
                    endpoint_ru="1369/json/900001.json",
                    raw_ru=MATCH_RU,
                    raw_en=MATCH_EN,
                    now=datetime.utcnow(),
                )

            payload_ru = service.game_details("900001", language="ru")
            payload_en = service.game_details("900001", language="en")
            self.assertIsNotNone(payload_ru)
            self.assertEqual(payload_ru["game"]["home"]["players"][0]["name"], "Иванов Иван")
            self.assertEqual(payload_en["game"]["away"]["players"][0]["name"], "Petrov Pyotr")
            self.assertEqual(len(payload_ru["referees"]), 2)
            self.assertEqual(payload_ru["details"]["home_players"], 1)
            self.assertEqual(payload_ru["team_statistics"]["segments"], ["total", "1", "2"])
            self.assertEqual(payload_ru["team_statistics"]["total"]["home"]["shots"], 31)
            self.assertEqual(payload_ru["team_statistics"]["periods"]["1"]["away"]["goals"], 0)
            self.assertEqual(payload_ru["game"]["home"]["players"][0]["statistics"]["points"], 3)
            self.assertEqual(payload_ru["game"]["home"]["players"][0]["statistics"]["faceoff_pct"], 60.0)
            self.assertEqual(payload_ru["game"]["away"]["players"][0]["statistics"]["saves"], 29)
            self.assertEqual(payload_ru["game"]["away"]["players"][0]["statistics"]["save_pct"], 93.55)
            self.assertTrue(payload_ru["game"]["player_statistics"]["available"])
            self.assertEqual(payload_ru["game"]["home"]["players"][0]["season_statistics"]["games"], 63)
            self.assertEqual(payload_ru["game"]["home"]["players"][0]["season_statistics"]["points"], 25)
            self.assertEqual(payload_ru["game"]["away"]["players"][0]["season_statistics"]["wins"], 25)
            self.assertEqual(payload_ru["game"]["away"]["players"][0]["season_statistics"]["save_pct"], 92)
            self.assertTrue(payload_ru["game"]["season_player_statistics"]["available"])
            database.engine.dispose()


if __name__ == "__main__":
    unittest.main()
