"""Clinic management application services — Phase 13.3."""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import get_event_publisher
from app.events.types import HealthcareEventType
from app.models.clinic_management import (
    ClinicPolicy,
    ClinicService,
    OperatingHoursPolicy,
    StaffAssignment,
)
from app.repositories.clinic_management import (
    ClinicPolicyRepository,
    ClinicServiceRepository,
    OperatingHoursPolicyRepository,
    StaffAssignmentRepository,
)
from app.repositories.foundation import BranchRepository, ClinicRepository, DepartmentRepository
from app.repositories.profiles import DoctorRepository
from app.schemas.clinic_management import (
    ClinicPolicyCreate,
    ClinicServiceCreate,
    OperatingHoursPolicyCreate,
    StaffAssignmentCreate,
)
from app.services.foundation import _actor_id
from app.validators import validate_code
from app.validators.clinic_management import validate_clinic_policy_windows, validate_weekly_hours
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class ClinicManagementService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.services = ClinicServiceRepository(session)
        self.staff = StaffAssignmentRepository(session)
        self.policies = ClinicPolicyRepository(session)
        self.hours = OperatingHoursPolicyRepository(session)
        self.clinics = ClinicRepository(session)
        self.branches = BranchRepository(session)
        self.departments = DepartmentRepository(session)
        self.doctors = DoctorRepository(session)
        self.events = get_event_publisher()

    async def create_service(
        self, tenant_id: UUID, body: ClinicServiceCreate, *, actor: CurrentUser | None = None
    ) -> ClinicService:
        if await self.clinics.get(tenant_id, body.clinic_id) is None:
            raise NotFoundError("کلینیک یافت نشد")
        if body.department_id and await self.departments.get(tenant_id, body.department_id) is None:
            raise NotFoundError("بخش یافت نشد")
        code = validate_code(body.code)
        if await self.services.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = ClinicService(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            department_id=body.department_id,
            code=code,
            name=body.name,
            description=body.description,
            duration_minutes=body.duration_minutes,
            pricing_ref=body.pricing_ref,
            status=body.status,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        try:
            await self.services.add(entity)
        except IntegrityError as exc:
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409) from exc
        self.events.publish(
            event_type=HealthcareEventType.CLINIC_SERVICE_CREATED,
            aggregate_type="clinic_service",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def create_staff_assignment(
        self, tenant_id: UUID, body: StaffAssignmentCreate, *, actor: CurrentUser | None = None
    ) -> StaffAssignment:
        if await self.clinics.get(tenant_id, body.clinic_id) is None:
            raise NotFoundError("کلینیک یافت نشد")
        if await self.doctors.get(tenant_id, body.doctor_id) is None:
            raise NotFoundError("پزشک یافت نشد")
        code = validate_code(body.code)
        if await self.staff.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = StaffAssignment(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            department_id=body.department_id,
            doctor_id=body.doctor_id,
            code=code,
            role_code=body.role_code,
            is_primary=body.is_primary,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.staff.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.STAFF_ASSIGNMENT_CREATED,
            aggregate_type="staff_assignment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def create_policy(
        self, tenant_id: UUID, body: ClinicPolicyCreate, *, actor: CurrentUser | None = None
    ) -> ClinicPolicy:
        if await self.clinics.get(tenant_id, body.clinic_id) is None:
            raise NotFoundError("کلینیک یافت نشد")
        validate_clinic_policy_windows(
            booking_lead_time_hours=body.booking_lead_time_hours,
            cancellation_window_hours=body.cancellation_window_hours,
        )
        code = validate_code(body.code)
        if await self.policies.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = ClinicPolicy(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            code=code,
            name=body.name,
            kind=body.kind,
            booking_lead_time_hours=body.booking_lead_time_hours,
            cancellation_window_hours=body.cancellation_window_hours,
            privacy_flags=body.privacy_flags,
            rules=body.rules,
            status=body.status,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.policies.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.CLINIC_POLICY_CREATED,
            aggregate_type="clinic_policy",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def create_operating_hours(
        self,
        tenant_id: UUID,
        body: OperatingHoursPolicyCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> OperatingHoursPolicy:
        if await self.clinics.get(tenant_id, body.clinic_id) is None:
            raise NotFoundError("کلینیک یافت نشد")
        if body.branch_id and await self.branches.get(tenant_id, body.branch_id) is None:
            raise NotFoundError("شعبه یافت نشد")
        validate_weekly_hours(body.weekly_hours)
        code = validate_code(body.code)
        if await self.hours.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = OperatingHoursPolicy(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            branch_id=body.branch_id,
            code=code,
            name=body.name,
            weekly_hours=body.weekly_hours,
            exceptions=body.exceptions,
            timezone=body.timezone,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.hours.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.OPERATING_HOURS_POLICY_CREATED,
            aggregate_type="operating_hours_policy",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_services(
        self, tenant_id: UUID, *, clinic_id: UUID | None = None, offset: int = 0, limit: int = 20
    ):
        if clinic_id:
            return list(await self.services.list_by_clinic(tenant_id, clinic_id, offset=offset, limit=limit))
        return list(await self.services.list_by_tenant(tenant_id, offset=offset, limit=limit))

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

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

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