from typing import Annotated, Any, Dict, Optional

from pydantic import Field, model_validator
from typing_extensions import Literal

from ....annotations.bounding_box import BoundingBox
from ....annotations.frame_size import FrameSize
from ....defects.nassco import NasscoDefects
from ....records.nassco.nassco_defects import NasscoDefectRecords
from ....translations.base import Language
from ....translations.nassco.defect_codes import NasscoDefectTranslations
from ....utils.defect_properties import DefectType
from ....utils.xml_models import XMLModel
from .validation_rules import PACPValidationRules


class PACPObservation(XMLModel):
    distance: Annotated[
        Optional[float],
        Field(default=None, ge=0, description=("The number displayed on the distance counter (e.g., 2.201). Supports 3 decimal places, though typically shown as 2 in output.")),
    ]
    video_ref: Annotated[Optional[str], Field(default=None, description=("The number displayed on the video counter (hh:mm:ss format). Special character is ':'"))]
    code: Annotated[
        NasscoDefectRecords,
        Field(description="Enter the PACP Code (uppercase, 2 to 6 alphabetic characters)"),
    ]
    continuous_defect: Annotated[
        Optional[str], Field(default=None, pattern=r"^[SF]\d{2}$", description="Alphanumeric Start (S01) or Finish (F01) label. Values range from 01 to 99.")
    ]
    severity: Annotated[Optional[int], Field(default=None, ge=1, le=5, description="Grade/Severity of the defect (1–5)")]
    value_1st_dimension: Annotated[Optional[str], Field(default=None, description="Measurement value for 1st dimension (decimal supported)")]
    value_2nd_dimension: Annotated[Optional[str], Field(default=None, description="Measurement value for 2nd dimension (decimal supported)")]
    value_defect_percent: Annotated[Optional[int], Field(default=None, ge=0, le=100, description="Defect value percentage (0–100)")]
    joint: Annotated[Optional[Literal["J"]], Field(default=None, pattern=r"^J$", description="Only a single letter 'J' is valid")]
    circumferential_location_from: Annotated[Optional[int], Field(default=None, ge=1, le=12, description="Starting clock position of defect (01–12)")]
    circumferential_location_to: Annotated[Optional[int], Field(default=None, ge=1, le=12, description="Ending clock position of defect (01–12)")]
    image_reference: Annotated[str, Field(default=..., description="Path or name of the image file associated with the defect. Captured automatically by the annotation tool.")]
    remarks: Annotated[Optional[str], Field(default=None, description="Free-form description or notes on the defect. Supports special characters, no character limit.")]
    bounding_box: Annotated[Optional[BoundingBox], Field(default=None, description="Bounding box of the defect")]
    size: Annotated[Optional[FrameSize], Field(default=None, description="Annotated frame of the defect")]
    skip_validation: Annotated[bool, Field(default=False, exclude=True, description="Skip PACP validation rules (for legacy data migration)")]

    def get_extra_fields(self) -> Dict[str, Any]:
        defect_type = NasscoDefects.get_defect_type(self.code)
        extra_fields = {
            "filename": self.image_reference,
            "object": {
                "name": self.code.full_name,
                "bndbox": self.bounding_box,
                "continuous_defect": self.continuous_defect,
                "distance": self.distance,
                "image_distance": None,
                "first_dimension": self.value_1st_dimension,
                "second_dimension": self.value_2nd_dimension,
                "defect_percentage": self.value_defect_percent if self.code is not NasscoDefectRecords.hole else None,
                "severity": self.severity,
                "joint": self.joint,
                "from": self.circumferential_location_from,
                "to": self.circumferential_location_to,
                "video_ref": self.video_ref,
                "remarks": self.remarks,
                "is_structural": 1 if defect_type == DefectType.structural else 0,
                "is_om": 1 if defect_type == DefectType.operational_and_maintenance else 0,
            },
        }
        return extra_fields

    class Config:
        ignore_fields = [
            "image_reference",
            "code",
            "bounding_box",
            "continuous_defect",
            "distance",
            "image_distance",
            "value_1st_dimension",
            "value_2nd_dimension",
            "value_defect_percent",
            "severity",
            "joint",
            "circumferential_location_from",
            "circumferential_location_to",
            "video_ref",
            "remarks",
            "is_structural",
            "is_om",
            "skip_validation",
        ]

    @property
    def pdf_observation_string(self) -> str:
        string_items = [self.code.label]

        if self.code is not NasscoDefectRecords.hole and self.value_defect_percent is not None:
            string_items.append(f"{int(self.value_defect_percent)}%")

        if self.circumferential_location_from is not None and self.circumferential_location_to is not None:
            string_items.append(f"from {self.circumferential_location_from:02d} to {self.circumferential_location_to:02d} o'clock")
        elif self.circumferential_location_from is not None:
            string_items.append(f"at {self.circumferential_location_from:02d} o'clock")
        elif self.circumferential_location_to is not None:
            string_items.append(f"at {self.circumferential_location_to:02d} o'clock")

        if self.remarks:
            string_items.append(self.remarks)
        string = ", ".join(string_items)
        return string

    @property
    def pdf_observation_string_spanish(self) -> str:
        string_items = [self.code.label]

        if self.code is not NasscoDefectRecords.hole and self.value_defect_percent is not None:
            if self.code is not NasscoDefectRecords.miscellaneous_water_level and self.code is not NasscoDefectRecords.miscellaneous_water_level_sag:
                string_items.append(f"{int(self.value_defect_percent)}% del área de corte trans")
            else:
                string_items.append(f"{int(self.value_defect_percent)}%")

        if self.circumferential_location_from is not None and self.circumferential_location_to is not None:
            string_items.append(f"de {self.circumferential_location_from:02d} en punto a {self.circumferential_location_to:02d} en punto")
        elif self.circumferential_location_from is not None:
            string_items.append(f"en {self.circumferential_location_from:02d} en punto")
        elif self.circumferential_location_to is not None:
            string_items.append(f"en {self.circumferential_location_to:02d} en punto")

        if self.remarks:
            string_items.append(self.remarks)

        if self.continuous_defect and self.continuous_defect.startswith("S"):
            string_items.append("Arrancar")

        if self.continuous_defect and self.continuous_defect.startswith("F"):
            string_items.append("Acabado")

        string = ", ".join(string_items)
        return NasscoDefectTranslations.translate_observation_string(string, Language.SPANISH)

    @classmethod
    def validate_all_fields(cls, values: Dict[str, Any]):
        """
        Validate all fields based on NASSCO PACP requirements.
        This method implements comprehensive validation rules for each defect code.
        """
        PACPValidationRules.validate_observation(values)
        return values

    @model_validator(mode="after")
    def validate_instance(self):
        if not self.skip_validation:
            values = self.model_dump(exclude={"skip_validation"})
            PACPValidationRules.validate_observation(values)
        return self
