"""Competition & Event Management services — Phase 9.7."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import SportsCenterEventType
from app.models.competitions import (
    Competition,
    CompetitionCertificate,
    CompetitionGroup,
    CompetitionJudge,
    CompetitionMatch,
    CompetitionMedal,
    CompetitionRanking,
    CompetitionRegistration,
    CompetitionResult,
    CompetitionTeam,
)
from app.models.types import MatchStatus, RegistrationStatus, ResultOutcome
from app.repositories.competitions import (
    CompetitionCertificateRepository,
    CompetitionGroupRepository,
    CompetitionJudgeRepository,
    CompetitionMatchRepository,
    CompetitionMedalRepository,
    CompetitionRankingRepository,
    CompetitionRegistrationRepository,
    CompetitionRepository,
    CompetitionResultRepository,
    CompetitionTeamRepository,
)
from app.repositories.foundation import CoachRepository, SportsCenterRepository
from app.repositories.members import MemberRepository
from app.schemas.competitions import (
    CompetitionCertificateCreate,
    CompetitionCreate,
    CompetitionGroupCreate,
    CompetitionJudgeCreate,
    CompetitionMatchCreate,
    CompetitionMedalCreate,
    CompetitionRegistrationCreate,
    CompetitionResultCreate,
    CompetitionTeamCreate,
    CompetitionUpdate,
)
from app.validators import ensure_optimistic_version, validate_code, validate_non_empty
from app.validators.members import validate_file_ref
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


def _apply_update(entity, data: dict) -> None:
    for key, value in data.items():
        if value is not None:
            setattr(entity, key, value)


class _CompetitionBase:
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        self.session = session
        self.publisher = publisher or get_event_publisher()
        self.centers = SportsCenterRepository(session)
        self.competitions = CompetitionRepository(session)

    async def _require_center(self, tenant_id: UUID, sports_center_id: UUID):
        center = await self.centers.get(tenant_id, sports_center_id)
        if center is None:
            raise NotFoundError("مرکز ورزشی یافت نشد", error_code="sports_center_not_found")
        return center

    async def _require_competition(self, tenant_id: UUID, competition_id: UUID) -> Competition:
        entity = await self.competitions.get(tenant_id, competition_id)
        if entity is None:
            raise NotFoundError("مسابقه یافت نشد", error_code="competition_not_found")
        return entity


class CompetitionService(_CompetitionBase):
    async def create(self, tenant_id: UUID, body: CompetitionCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        validate_code(body.code)
        validate_non_empty(body.name, field="name")
        actor_id = actor.user_id if actor else None
        entity = Competition(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            code=body.code,
            name=body.name,
            description=body.description,
            competition_kind=body.competition_kind,
            sport_id=body.sport_id,
            facility_id=body.facility_id,
            status=body.status,
            starts_on=body.starts_on,
            ends_on=body.ends_on,
            max_teams=body.max_teams,
            rules=body.rules,
            metadata_json=body.metadata_json,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.competitions.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_CREATED,
            aggregate_type="competition",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": body.code},
        )
        return entity

    async def list(self, tenant_id: UUID, *, offset=0, limit=20):
        return await self.competitions.list_by_tenant(tenant_id, offset=offset, limit=limit)

    async def get(self, tenant_id: UUID, entity_id: UUID):
        return await self._require_competition(tenant_id, entity_id)

    async def update(self, tenant_id: UUID, entity_id: UUID, body: CompetitionUpdate, *, actor: CurrentUser):
        entity = await self._require_competition(tenant_id, entity_id)
        ensure_optimistic_version(entity, body.version)
        _apply_update(entity, body.model_dump(exclude_unset=True, exclude={"version"}))
        entity.version += 1
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_UPDATED,
            aggregate_type="competition",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        return entity


class CompetitionTeamService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionTeamRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionTeamCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        competition = await self._require_competition(tenant_id, body.competition_id)
        if competition.max_teams:
            count = await self.repo.count_by_competition(tenant_id, body.competition_id)
            if count >= competition.max_teams:
                raise AppError("ظرفیت تیم‌ها تکمیل است", status_code=409, error_code="team_capacity_full")
        validate_code(body.code)
        actor_id = actor.user_id if actor else None
        entity = CompetitionTeam(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            code=body.code,
            name=body.name,
            captain_member_id=body.captain_member_id,
            group_id=body.group_id,
            metadata_json=body.metadata_json,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_TEAM_CREATED,
            aggregate_type="competition_team",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"competition_id": str(body.competition_id), "code": body.code},
        )
        return entity

    async def list(self, tenant_id: UUID, competition_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_competition(tenant_id, competition_id, offset=offset, limit=limit)


class CompetitionGroupService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionGroupRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionGroupCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        validate_code(body.code)
        actor_id = actor.user_id if actor else None
        entity = CompetitionGroup(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            code=body.code,
            name=body.name,
            sort_order=body.sort_order,
            metadata_json=body.metadata_json,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class CompetitionRegistrationService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionRegistrationRepository(session)
        self.members = MemberRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionRegistrationCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        member = await self.members.get(tenant_id, body.member_id)
        if member is None:
            raise NotFoundError("عضو یافت نشد", error_code="member_not_found")
        actor_id = actor.user_id if actor else None
        entity = CompetitionRegistration(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            member_id=body.member_id,
            team_id=body.team_id,
            status=RegistrationStatus.PENDING,
            registered_at=datetime.now(timezone.utc),
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_REGISTRATION_CREATED,
            aggregate_type="competition_registration",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"competition_id": str(body.competition_id), "member_id": str(body.member_id)},
        )
        return entity

    async def confirm(self, tenant_id: UUID, registration_id: UUID, *, actor: CurrentUser):
        entity = await self.repo.get(tenant_id, registration_id)
        if entity is None:
            raise NotFoundError("ثبت‌نام یافت نشد", error_code="registration_not_found")
        entity.status = RegistrationStatus.CONFIRMED
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_REGISTRATION_CONFIRMED,
            aggregate_type="competition_registration",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"competition_id": str(entity.competition_id)},
        )
        return entity


class CompetitionMatchService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionMatchRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionMatchCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        validate_code(body.code)
        actor_id = actor.user_id if actor else None
        entity = CompetitionMatch(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            group_id=body.group_id,
            code=body.code,
            home_team_id=body.home_team_id,
            away_team_id=body.away_team_id,
            facility_id=body.facility_id,
            scheduled_at=body.scheduled_at,
            status=MatchStatus.SCHEDULED,
            round_number=body.round_number,
            metadata_json=body.metadata_json,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_MATCH_SCHEDULED,
            aggregate_type="competition_match",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"competition_id": str(body.competition_id), "code": body.code},
        )
        return entity


class CompetitionResultService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionResultRepository(session)
        self.matches = CompetitionMatchRepository(session)
        self.rankings = CompetitionRankingRepository(session)

    async def _update_rankings(
        self,
        tenant_id: UUID,
        competition_id: UUID,
        home_team_id: UUID | None,
        away_team_id: UUID | None,
        outcome: ResultOutcome,
        winner_team_id: UUID | None,
    ) -> None:
        competition = await self._require_competition(tenant_id, competition_id)

        async def bump(team_id: UUID, *, win: bool = False, draw: bool = False, loss: bool = False) -> None:
            ranking = await self.rankings.get_by_team(tenant_id, competition_id, team_id)
            if ranking is None:
                ranking = CompetitionRanking(
                    tenant_id=tenant_id,
                    sports_center_id=competition.sports_center_id,
                    competition_id=competition_id,
                    team_id=team_id,
                    rank=0,
                    points=0,
                    wins=0,
                    losses=0,
                    draws=0,
                )
                await self.rankings.add(ranking)
            if win:
                ranking.wins += 1
                ranking.points = float(ranking.points) + 3
            elif draw:
                ranking.draws += 1
                ranking.points = float(ranking.points) + 1
            elif loss:
                ranking.losses += 1

        if outcome == ResultOutcome.DRAW:
            if home_team_id:
                await bump(home_team_id, draw=True)
            if away_team_id:
                await bump(away_team_id, draw=True)
        else:
            if winner_team_id == home_team_id:
                if home_team_id:
                    await bump(home_team_id, win=True)
                if away_team_id:
                    await bump(away_team_id, loss=True)
            elif winner_team_id == away_team_id:
                if away_team_id:
                    await bump(away_team_id, win=True)
                if home_team_id:
                    await bump(home_team_id, loss=True)

    async def create(self, tenant_id: UUID, body: CompetitionResultCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        match = await self.matches.get(tenant_id, body.match_id)
        if match is None:
            raise NotFoundError("مسابقه/بازی یافت نشد", error_code="match_not_found")
        actor_id = actor.user_id if actor else None
        now = datetime.now(timezone.utc)
        entity = CompetitionResult(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            match_id=body.match_id,
            home_score=body.home_score,
            away_score=body.away_score,
            winner_team_id=body.winner_team_id,
            outcome=body.outcome,
            recorded_at=now,
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        match.status = MatchStatus.COMPLETED
        await self._update_rankings(
            tenant_id,
            body.competition_id,
            match.home_team_id,
            match.away_team_id,
            body.outcome,
            body.winner_team_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_RESULT_RECORDED,
            aggregate_type="competition_result",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"match_id": str(body.match_id), "outcome": body.outcome.value},
        )
        return entity

    async def list_rankings(self, tenant_id: UUID, competition_id: UUID, *, offset=0, limit=20):
        await self._require_competition(tenant_id, competition_id)
        rows = await self.rankings.list_by_competition(tenant_id, competition_id, offset=offset, limit=limit)
        for idx, row in enumerate(rows, start=1):
            row.rank = idx
        await self.session.commit()
        return rows


class CompetitionMedalService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionMedalRepository(session)
        self.members = MemberRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionMedalCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        member = await self.members.get(tenant_id, body.member_id)
        if member is None:
            raise NotFoundError("عضو یافت نشد", error_code="member_not_found")
        actor_id = actor.user_id if actor else None
        entity = CompetitionMedal(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            member_id=body.member_id,
            team_id=body.team_id,
            medal_kind=body.medal_kind,
            awarded_at=datetime.now(timezone.utc),
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_MEDAL_AWARDED,
            aggregate_type="competition_medal",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"medal_kind": body.medal_kind.value},
        )
        return entity


class CompetitionJudgeService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionJudgeRepository(session)
        self.coaches = CoachRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionJudgeCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        coach = await self.coaches.get(tenant_id, body.coach_id)
        if coach is None:
            raise NotFoundError("داور/مربی یافت نشد", error_code="coach_not_found")
        actor_id = actor.user_id if actor else None
        entity = CompetitionJudge(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            coach_id=body.coach_id,
            role=body.role,
            match_id=body.match_id,
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class CompetitionCertificateService(_CompetitionBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = CompetitionCertificateRepository(session)
        self.members = MemberRepository(session)

    async def create(self, tenant_id: UUID, body: CompetitionCertificateCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_competition(tenant_id, body.competition_id)
        member = await self.members.get(tenant_id, body.member_id)
        if member is None:
            raise NotFoundError("عضو یافت نشد", error_code="member_not_found")
        if body.file_ref:
            validate_file_ref(body.file_ref)
        actor_id = actor.user_id if actor else None
        entity = CompetitionCertificate(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            competition_id=body.competition_id,
            member_id=body.member_id,
            title=body.title,
            file_ref=body.file_ref,
            issued_at=datetime.now(timezone.utc),
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=SportsCenterEventType.COMPETITION_CERTIFICATE_ISSUED,
            aggregate_type="competition_certificate",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"competition_id": str(body.competition_id)},
        )
        return entity
