from __future__ import annotations

from enum import StrEnum
from typing import Any

from pydantic import Field, model_validator

from app.rda.domain.models import DomainModel, RdaArtifactType


class ValidationSeverity(StrEnum):
    ERROR = "error"
    WARNING = "warning"


class FhirLikeResourceType(StrEnum):
    BUNDLE = "bundle"
    COMPOSITION = "composition"
    PATIENT = "patient"
    ENCOUNTER = "encounter"
    ORGANIZATION = "organization"
    PRACTITIONER = "practitioner"
    DOCUMENT_REFERENCE = "document_reference"


class RdaValidationFinding(DomainModel):
    severity: ValidationSeverity
    resource_type: FhirLikeResourceType
    resource_path: str
    rule_code: str
    message: str
    observed_value: Any = None


class RdaValidationResult(DomainModel):
    artifact_type: RdaArtifactType
    is_valid: bool
    error_count: int
    warning_count: int
    findings: list[RdaValidationFinding] = Field(default_factory=list)

    @model_validator(mode="after")
    def validate_summary(self) -> "RdaValidationResult":
        error_count = sum(1 for item in self.findings if item.severity == ValidationSeverity.ERROR)
        warning_count = sum(1 for item in self.findings if item.severity == ValidationSeverity.WARNING)
        is_valid = error_count == 0

        if self.error_count != error_count:
            raise ValueError("error_count debe coincidir con los hallazgos de severidad error.")
        if self.warning_count != warning_count:
            raise ValueError("warning_count debe coincidir con los hallazgos de severidad warning.")
        if self.is_valid != is_valid:
            raise ValueError("is_valid debe ser false cuando existan hallazgos error.")
        return self

    @classmethod
    def from_findings(
        cls,
        *,
        artifact_type: RdaArtifactType,
        findings: list[RdaValidationFinding] | None = None,
    ) -> "RdaValidationResult":
        findings = list(findings or [])
        error_count = sum(1 for item in findings if item.severity == ValidationSeverity.ERROR)
        warning_count = sum(1 for item in findings if item.severity == ValidationSeverity.WARNING)
        return cls(
            artifact_type=artifact_type,
            is_valid=error_count == 0,
            error_count=error_count,
            warning_count=warning_count,
            findings=findings,
        )


class RdaFhirProjectionRule(DomainModel):
    artifact_type: RdaArtifactType
    resource_type: FhirLikeResourceType
    source_paths: list[str] = Field(default_factory=list)
    required: bool = True
    activation_note: str | None = None


RDA_FHIR_PROJECTION_MATRIX: tuple[RdaFhirProjectionRule, ...] = (
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.PATIENT,
        resource_type=FhirLikeResourceType.BUNDLE,
        source_paths=["payload"],
        activation_note="Bundle contenedor del resumen paciente en representación FHIR-like.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.PATIENT,
        resource_type=FhirLikeResourceType.COMPOSITION,
        source_paths=["payload.summary_type", "payload.patient", "payload.organization", "payload.diagnoses"],
        activation_note="Composition resume secciones clínicas y organizacionales del artefacto patient.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.PATIENT,
        resource_type=FhirLikeResourceType.PATIENT,
        source_paths=["payload.patient"],
        activation_note="Patient toma identidad y demografía mínima del bloque patient.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.PATIENT,
        resource_type=FhirLikeResourceType.ORGANIZATION,
        source_paths=["payload.organization.provider", "payload.organization.payer"],
        activation_note="Organization representa proveedor y/o pagador cuando existan datos organizacionales.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.BUNDLE,
        source_paths=["payload"],
        activation_note="Bundle contenedor del resumen de urgencias en representación FHIR-like.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.COMPOSITION,
        source_paths=["payload.summary_type", "payload.patient", "payload.encounter", "payload.clinical"],
        activation_note="Composition resume contexto de atención y secciones clínicas del artefacto emergency.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.PATIENT,
        source_paths=["payload.patient"],
        activation_note="Patient toma identidad mínima del bloque patient del resumen de urgencias.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.ENCOUNTER,
        source_paths=["payload.encounter"],
        activation_note="Encounter representa el episodio urgente y debe referenciar al Patient.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.ORGANIZATION,
        source_paths=["payload.encounter.provider", "payload.encounter.payer"],
        activation_note="Organization representa proveedor y/o pagador del episodio cuando existan datos.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.PRACTITIONER,
        source_paths=["payload.encounter.responsible_professional"],
        required=False,
        activation_note="Practitioner solo se proyecta si existe professional responsable en el encounter.",
    ),
    RdaFhirProjectionRule(
        artifact_type=RdaArtifactType.EMERGENCY,
        resource_type=FhirLikeResourceType.DOCUMENT_REFERENCE,
        source_paths=["section_trace", "missing_fields"],
        required=False,
        activation_note="DocumentReference no es obligatorio en v1; su ausencia debe tratarse como warning o brecha documentada.",
    ),
)
