"""Medical record application services — Phase 13.5."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import get_event_publisher
from app.events.types import HealthcareEventType
from app.models.medical_records import (
    Allergy,
    Condition,
    Encounter,
    ImmunizationRecord,
    MedicalRecord,
    MedicalRecordAccessLog,
    MedicationStatement,
)
from app.models.types import EncounterStatus
from app.policies.medical_records import MedicalRecordAccessPolicy
from app.repositories.foundation import ClinicRepository
from app.repositories.medical_records import (
    AllergyRepository,
    ConditionRepository,
    EncounterRepository,
    ImmunizationRecordRepository,
    MedicalRecordAccessLogRepository,
    MedicalRecordRepository,
    MedicationStatementRepository,
)
from app.repositories.profiles import DoctorRepository, PatientRepository
from app.schemas.medical_records import (
    AllergyCreate,
    ConditionCreate,
    EncounterCreate,
    EncounterFinalize,
    EncounterUpdate,
    ImmunizationCreate,
    MedicalRecordAccessRequest,
    MedicalRecordCreate,
    MedicationCreate,
)
from app.services.foundation import _actor_id
from app.validators import ensure_optimistic_version, validate_code
from app.validators.medical_records import ensure_encounter_can_finalize, ensure_encounter_mutable
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class MedicalRecordService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.records = MedicalRecordRepository(session)
        self.encounters = EncounterRepository(session)
        self.conditions = ConditionRepository(session)
        self.allergies = AllergyRepository(session)
        self.medications = MedicationStatementRepository(session)
        self.immunizations = ImmunizationRecordRepository(session)
        self.access_logs = MedicalRecordAccessLogRepository(session)
        self.clinics = ClinicRepository(session)
        self.patients = PatientRepository(session)
        self.doctors = DoctorRepository(session)
        self.access_policy = MedicalRecordAccessPolicy()
        self.events = get_event_publisher()

    async def _log_access(
        self,
        tenant_id: UUID,
        medical_record_id: UUID,
        *,
        actor_user_id: str | None,
        action: str,
        reason: str | None = None,
    ) -> None:
        log = MedicalRecordAccessLog(
            tenant_id=tenant_id,
            medical_record_id=medical_record_id,
            actor_user_id=actor_user_id or "unknown",
            action=action,
            reason=reason,
        )
        await self.access_logs.add(log)
        self.events.publish(
            event_type=HealthcareEventType.MEDICAL_RECORD_ACCESSED,
            aggregate_type="medical_record",
            aggregate_id=medical_record_id,
            tenant_id=tenant_id,
            payload={"action": action},
        )

    async def create_record(
        self, tenant_id: UUID, body: MedicalRecordCreate, *, actor: CurrentUser | None = None
    ) -> MedicalRecord:
        if await self.clinics.get(tenant_id, body.clinic_id) is None:
            raise NotFoundError("کلینیک یافت نشد")
        if await self.patients.get(tenant_id, body.patient_id) is None:
            raise NotFoundError("بیمار یافت نشد")
        code = validate_code(body.code)
        if await self.records.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = MedicalRecord(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            patient_id=body.patient_id,
            code=code,
            access_policy=body.access_policy,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.records.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.MEDICAL_RECORD_CREATED,
            aggregate_type="medical_record",
            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 get_record(
        self,
        tenant_id: UUID,
        record_id: UUID,
        *,
        actor: CurrentUser | None = None,
        access: MedicalRecordAccessRequest | None = None,
    ) -> MedicalRecord:
        entity = await self.records.get(tenant_id, record_id)
        if entity is None:
            raise NotFoundError("پرونده پزشکی یافت نشد")
        self.access_policy.ensure_read_access(
            access_policy=entity.access_policy,
            actor_user_id=_actor_id(actor) or "unknown",
        )
        await self._log_access(
            tenant_id,
            entity.id,
            actor_user_id=_actor_id(actor),
            action="read",
            reason=access.reason if access else None,
        )
        await self.session.commit()
        return entity

    async def create_encounter(
        self, tenant_id: UUID, body: EncounterCreate, *, actor: CurrentUser | None = None
    ) -> Encounter:
        if await self.records.get(tenant_id, body.medical_record_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.encounters.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = Encounter(
            tenant_id=tenant_id,
            medical_record_id=body.medical_record_id,
            visit_session_id=body.visit_session_id,
            appointment_id=body.appointment_id,
            doctor_id=body.doctor_id,
            code=code,
            status=EncounterStatus.DRAFT,
            chief_complaint=body.chief_complaint,
            clinical_summary=body.clinical_summary,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.encounters.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.ENCOUNTER_CREATED,
            aggregate_type="encounter",
            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 update_encounter(
        self,
        tenant_id: UUID,
        encounter_id: UUID,
        body: EncounterUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Encounter:
        entity = await self.encounters.get(tenant_id, encounter_id)
        if entity is None:
            raise NotFoundError("Encounter یافت نشد")
        ensure_encounter_mutable(entity.status)
        ensure_optimistic_version(entity, body.version)
        if body.chief_complaint is not None:
            entity.chief_complaint = body.chief_complaint
        if body.clinical_summary is not None:
            entity.clinical_summary = body.clinical_summary
        if body.metadata_json is not None:
            entity.metadata_json = body.metadata_json
        if entity.status == EncounterStatus.DRAFT:
            entity.status = EncounterStatus.IN_PROGRESS
        entity.version += 1
        entity.updated_by = _actor_id(actor)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def finalize_encounter(
        self,
        tenant_id: UUID,
        encounter_id: UUID,
        body: EncounterFinalize,
        *,
        actor: CurrentUser | None = None,
    ) -> Encounter:
        entity = await self.encounters.get(tenant_id, encounter_id)
        if entity is None:
            raise NotFoundError("Encounter یافت نشد")
        ensure_encounter_can_finalize(entity.status)
        ensure_optimistic_version(entity, body.version)
        entity.status = EncounterStatus.FINALIZED
        entity.finalized_at = datetime.now(timezone.utc)
        entity.addendum = body.addendum
        entity.version += 1
        entity.updated_by = _actor_id(actor)
        self.events.publish(
            event_type=HealthcareEventType.ENCOUNTER_FINALIZED,
            aggregate_type="encounter",
            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 add_condition(
        self, tenant_id: UUID, record_id: UUID, body: ConditionCreate, *, actor=None
    ) -> Condition:
        record = await self.records.get(tenant_id, record_id)
        if record is None:
            raise NotFoundError("پرونده پزشکی یافت نشد")
        code = validate_code(body.code)
        entity = Condition(
            tenant_id=tenant_id,
            medical_record_id=record_id,
            code=code,
            display=body.display,
            clinical_status=body.clinical_status,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.conditions.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_allergy(
        self, tenant_id: UUID, record_id: UUID, body: AllergyCreate, *, actor=None
    ) -> Allergy:
        if await self.records.get(tenant_id, record_id) is None:
            raise NotFoundError("پرونده پزشکی یافت نشد")
        entity = Allergy(
            tenant_id=tenant_id,
            medical_record_id=record_id,
            code=validate_code(body.code),
            substance=body.substance,
            severity=body.severity,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.allergies.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_medication(
        self, tenant_id: UUID, record_id: UUID, body: MedicationCreate, *, actor=None
    ) -> MedicationStatement:
        if await self.records.get(tenant_id, record_id) is None:
            raise NotFoundError("پرونده پزشکی یافت نشد")
        entity = MedicationStatement(
            tenant_id=tenant_id,
            medical_record_id=record_id,
            code=validate_code(body.code),
            medication_code=body.medication_code,
            display=body.display,
            dosage=body.dosage,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.medications.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def add_immunization(
        self, tenant_id: UUID, record_id: UUID, body: ImmunizationCreate, *, actor=None
    ) -> ImmunizationRecord:
        if await self.records.get(tenant_id, record_id) is None:
            raise NotFoundError("پرونده پزشکی یافت نشد")
        entity = ImmunizationRecord(
            tenant_id=tenant_id,
            medical_record_id=record_id,
            code=validate_code(body.code),
            vaccine_code=body.vaccine_code,
            administered_on=body.administered_on,
            storage_file_ref=body.storage_file_ref,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.immunizations.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_clinical_lists(self, tenant_id: UUID, record_id: UUID):
        return {
            "conditions": list(await self.conditions.list_by_record(tenant_id, record_id)),
            "allergies": list(await self.allergies.list_by_record(tenant_id, record_id)),
            "medications": list(await self.medications.list_by_record(tenant_id, record_id)),
            "immunizations": list(await self.immunizations.list_by_record(tenant_id, record_id)),
        }

    async def list_access_logs(self, tenant_id: UUID, record_id: UUID):
        return list(await self.access_logs.list_by_record(tenant_id, record_id))
