"""Coach & Staff Management services — Phase 9.3."""
from __future__ import annotations

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.staff import (
    StaffAssignment,
    StaffAvailability,
    StaffCertificate,
    StaffSkill,
    StaffWorkingHours,
)
from app.models.types import AuditAction
from app.repositories.foundation import CoachRepository, SportsCenterRepository
from app.repositories.members import MemberRepository
from app.repositories.staff import (
    StaffAssignmentRepository,
    StaffAvailabilityRepository,
    StaffCertificateRepository,
    StaffSkillRepository,
    StaffWorkingHoursRepository,
)
from app.schemas.staff import (
    StaffAssignmentCreate,
    StaffAssignmentUpdate,
    StaffAvailabilityCreate,
    StaffAvailabilityUpdate,
    StaffCertificateCreate,
    StaffCertificateUpdate,
    StaffSkillCreate,
    StaffSkillUpdate,
    StaffWorkingHoursCreate,
    StaffWorkingHoursUpdate,
)
from app.services.audit_service import AuditService
from app.validators import validate_code, validate_non_empty
from app.validators.members import validate_file_ref
from app.validators.staff import (
    validate_assignment_kind,
    validate_datetime_window,
    validate_day_of_week,
    validate_time_window,
)
from shared.exceptions import 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 _StaffBase:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.audit = AuditService(session)
        self.publisher = publisher or get_event_publisher()
        self.centers = SportsCenterRepository(session)
        self.coaches = CoachRepository(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_coach(self, tenant_id: UUID, coach_id: UUID):
        coach = await self.coaches.get(tenant_id, coach_id)
        if coach is None:
            raise NotFoundError("مربی/پرسنل یافت نشد", error_code="coach_not_found")
        return coach


class StaffCertificateService(_StaffBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = StaffCertificateRepository(session)

    async def create(self, tenant_id: UUID, body: StaffCertificateCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_coach(tenant_id, body.coach_id)
        validate_non_empty(body.name, field="name")
        if body.file_ref:
            validate_file_ref(body.file_ref)
        entity = StaffCertificate(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            coach_id=body.coach_id,
            name=body.name,
            issuer=body.issuer,
            certificate_number=body.certificate_number,
            issued_on=body.issued_on,
            expires_on=body.expires_on,
            file_ref=body.file_ref,
            status=body.status,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_CERTIFICATE_CREATED,
            aggregate_type="staff_certificate",
            aggregate_id=entity.id,
            payload={"coach_id": str(body.coach_id), "name": body.name},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("گواهینامه یافت نشد", error_code="staff_certificate_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: StaffCertificateUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        data = body.model_dump(exclude_unset=True)
        if "file_ref" in data and data["file_ref"]:
            validate_file_ref(data["file_ref"])
        _apply_update(entity, data)
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_CERTIFICATE_UPDATED,
            aggregate_type="staff_certificate",
            aggregate_id=entity.id,
            payload={"coach_id": str(entity.coach_id)},
        )
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class StaffSkillService(_StaffBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = StaffSkillRepository(session)

    async def create(self, tenant_id: UUID, body: StaffSkillCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_coach(tenant_id, body.coach_id)
        validate_code(body.skill_code)
        validate_non_empty(body.skill_name, field="skill_name")
        entity = StaffSkill(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            coach_id=body.coach_id,
            skill_code=body.skill_code,
            skill_name=body.skill_name,
            level=body.level,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_SKILL_CREATED,
            aggregate_type="staff_skill",
            aggregate_id=entity.id,
            payload={"coach_id": str(body.coach_id), "skill_code": body.skill_code},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("مهارت یافت نشد", error_code="staff_skill_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: StaffSkillUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        _apply_update(entity, body.model_dump(exclude_unset=True))
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class StaffWorkingHoursService(_StaffBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = StaffWorkingHoursRepository(session)

    async def create(self, tenant_id: UUID, body: StaffWorkingHoursCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_coach(tenant_id, body.coach_id)
        validate_day_of_week(body.day_of_week)
        validate_time_window(body.starts_at, body.ends_at)
        entity = StaffWorkingHours(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            coach_id=body.coach_id,
            branch_id=body.branch_id,
            day_of_week=body.day_of_week,
            starts_at=body.starts_at,
            ends_at=body.ends_at,
            is_active=body.is_active,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_WORKING_HOURS_CREATED,
            aggregate_type="staff_working_hours",
            aggregate_id=entity.id,
            payload={"coach_id": str(body.coach_id)},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("ساعات کاری یافت نشد", error_code="staff_working_hours_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: StaffWorkingHoursUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        data = body.model_dump(exclude_unset=True)
        starts = data.get("starts_at", entity.starts_at)
        ends = data.get("ends_at", entity.ends_at)
        validate_time_window(starts, ends)
        if "day_of_week" in data:
            validate_day_of_week(data["day_of_week"])
        _apply_update(entity, data)
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class StaffAvailabilityService(_StaffBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = StaffAvailabilityRepository(session)

    async def create(self, tenant_id: UUID, body: StaffAvailabilityCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_coach(tenant_id, body.coach_id)
        validate_datetime_window(body.starts_at, body.ends_at)
        entity = StaffAvailability(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            coach_id=body.coach_id,
            starts_at=body.starts_at,
            ends_at=body.ends_at,
            is_available=body.is_available,
            reason=body.reason,
            metadata_json=body.metadata_json,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_AVAILABILITY_CHANGED,
            aggregate_type="staff_availability",
            aggregate_id=entity.id,
            payload={"coach_id": str(body.coach_id), "is_available": body.is_available},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("دسترس‌پذیری یافت نشد", error_code="staff_availability_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: StaffAvailabilityUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        data = body.model_dump(exclude_unset=True)
        starts = data.get("starts_at", entity.starts_at)
        ends = data.get("ends_at", entity.ends_at)
        validate_datetime_window(starts, ends)
        _apply_update(entity, data)
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_AVAILABILITY_CHANGED,
            aggregate_type="staff_availability",
            aggregate_id=entity.id,
            payload={"coach_id": str(entity.coach_id), "is_available": entity.is_available},
        )
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class StaffAssignmentService(_StaffBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = StaffAssignmentRepository(session)
        self.members = MemberRepository(session)

    async def create(self, tenant_id: UUID, body: StaffAssignmentCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_coach(tenant_id, body.coach_id)
        validate_assignment_kind(body.assignment_kind)
        if body.member_id:
            member = await self.members.get(tenant_id, body.member_id)
            if member is None:
                raise NotFoundError("عضو یافت نشد", error_code="member_not_found")
        entity = StaffAssignment(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            coach_id=body.coach_id,
            assignment_kind=body.assignment_kind,
            member_id=body.member_id,
            sport_id=body.sport_id,
            program_ref=body.program_ref,
            starts_on=body.starts_on,
            ends_on=body.ends_on,
            status=body.status,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.STAFF_ASSIGNMENT_CREATED,
            aggregate_type="staff_assignment",
            aggregate_id=entity.id,
            payload={"coach_id": str(body.coach_id), "assignment_kind": body.assignment_kind.value},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("تخصیص یافت نشد", error_code="staff_assignment_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: StaffAssignmentUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        data = body.model_dump(exclude_unset=True)
        if "assignment_kind" in data:
            validate_assignment_kind(data["assignment_kind"])
        _apply_update(entity, data)
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity
