"""Doctor and Patient application services — Phase 13.0."""
from __future__ import annotations

from datetime import date, datetime
from typing import Any
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.profiles import Doctor, Patient
from app.models.types import AuditAction
from app.repositories.foundation import ClinicRepository, DepartmentRepository
from app.repositories.profiles import DoctorRepository, PatientRepository
from app.schemas.profiles import (
    DoctorCreate,
    DoctorUpdate,
    PatientCreate,
    PatientUpdate,
)
from app.services.audit_service import AuditService
from app.services.foundation import _actor_id, _apply_update, _jsonable_changes
from app.validators import ensure_optimistic_version, validate_code, validate_non_empty
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class DoctorService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = DoctorRepository(session)
        self.clinics = ClinicRepository(session)
        self.departments = DepartmentRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()

    async def create(
        self,
        tenant_id: UUID,
        body: DoctorCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> Doctor:
        user_id = validate_non_empty(body.user_id, "user_id")
        code = validate_code(body.code)
        display_name = validate_non_empty(body.display_name, "display_name")
        if body.clinic_id is not None:
            clinic = await self.clinics.get(tenant_id, body.clinic_id)
            if clinic is None:
                raise NotFoundError("کلینیک یافت نشد")
        if body.department_id is not None:
            department = await self.departments.get(tenant_id, body.department_id)
            if department is None:
                raise NotFoundError("بخش یافت نشد")
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError(
                "کد پزشک تکراری است",
                error_code="duplicate_code",
                status_code=409,
            )
        if await self.repo.get_by_user_id(tenant_id, user_id):
            raise AppError(
                "پروفایل پزشک برای این کاربر وجود دارد",
                error_code="duplicate_user",
                status_code=409,
            )
        entity = Doctor(
            tenant_id=tenant_id,
            user_id=user_id,
            clinic_id=body.clinic_id,
            department_id=body.department_id,
            code=code,
            display_name=display_name,
            specialty=body.specialty,
            license_number=body.license_number,
            status=body.status,
            contact=body.contact,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        try:
            await self.repo.add(entity)
        except IntegrityError as exc:
            raise AppError(
                "پروفایل پزشک تکراری است",
                error_code="duplicate_code",
                status_code=409,
            ) from exc
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="doctor",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=_actor_id(actor),
            changes=_jsonable_changes(body.model_dump()),
        )
        self.events.publish(
            event_type=HealthcareEventType.DOCTOR_CREATED,
            aggregate_type="doctor",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "user_id": entity.user_id},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> Doctor:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("پزشک یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20
    ) -> list[Doctor]:
        return list(await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit))

    async def update(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        body: DoctorUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Doctor:
        entity = await self.get(tenant_id, entity_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "display_name" in data and data["display_name"] is not None:
            data["display_name"] = validate_non_empty(data["display_name"], "display_name")
        if "clinic_id" in data and data["clinic_id"] is not None:
            clinic = await self.clinics.get(tenant_id, data["clinic_id"])
            if clinic is None:
                raise NotFoundError("کلینیک یافت نشد")
        if "department_id" in data and data["department_id"] is not None:
            department = await self.departments.get(tenant_id, data["department_id"])
            if department is None:
                raise NotFoundError("بخش یافت نشد")
        _apply_update(entity, data)
        entity.version += 1
        entity.updated_by = _actor_id(actor)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="doctor",
            entity_id=entity.id,
            action=AuditAction.UPDATE,
            actor_user_id=_actor_id(actor),
            changes=_jsonable_changes(data),
        )
        self.events.publish(
            event_type=HealthcareEventType.DOCTOR_UPDATED,
            aggregate_type="doctor",
            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 soft_delete(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        *,
        actor: CurrentUser | None = None,
    ) -> Doctor:
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=_actor_id(actor))
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="doctor",
            entity_id=entity.id,
            action=AuditAction.DELETE,
            actor_user_id=_actor_id(actor),
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class PatientService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = PatientRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()

    async def create(
        self,
        tenant_id: UUID,
        body: PatientCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> Patient:
        user_id = validate_non_empty(body.user_id, "user_id")
        code = validate_code(body.code)
        display_name = validate_non_empty(body.display_name, "display_name")
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError(
                "کد بیمار تکراری است",
                error_code="duplicate_code",
                status_code=409,
            )
        if await self.repo.get_by_user_id(tenant_id, user_id):
            raise AppError(
                "پروفایل بیمار برای این کاربر وجود دارد",
                error_code="duplicate_user",
                status_code=409,
            )
        entity = Patient(
            tenant_id=tenant_id,
            user_id=user_id,
            code=code,
            display_name=display_name,
            status=body.status,
            date_of_birth=body.date_of_birth,
            gender=body.gender,
            contact=body.contact,
            emergency_contact=body.emergency_contact,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        try:
            await self.repo.add(entity)
        except IntegrityError as exc:
            raise AppError(
                "پروفایل بیمار تکراری است",
                error_code="duplicate_code",
                status_code=409,
            ) from exc
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="patient",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=_actor_id(actor),
            changes=_jsonable_changes(body.model_dump()),
        )
        self.events.publish(
            event_type=HealthcareEventType.PATIENT_CREATED,
            aggregate_type="patient",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "user_id": entity.user_id},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

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

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20
    ) -> list[Patient]:
        return list(await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit))

    async def update(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        body: PatientUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Patient:
        entity = await self.get(tenant_id, entity_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "display_name" in data and data["display_name"] is not None:
            data["display_name"] = validate_non_empty(data["display_name"], "display_name")
        _apply_update(entity, data)
        entity.version += 1
        entity.updated_by = _actor_id(actor)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="patient",
            entity_id=entity.id,
            action=AuditAction.UPDATE,
            actor_user_id=_actor_id(actor),
            changes=_jsonable_changes(data),
        )
        self.events.publish(
            event_type=HealthcareEventType.PATIENT_UPDATED,
            aggregate_type="patient",
            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 soft_delete(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        *,
        actor: CurrentUser | None = None,
    ) -> Patient:
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=_actor_id(actor))
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="patient",
            entity_id=entity.id,
            action=AuditAction.DELETE,
            actor_user_id=_actor_id(actor),
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity
