import os
from pathlib import Path
from typing import Optional

from typing_extensions import Literal

from ...config.pdf_config import PDFConfig
from ...models.nassco.pacp.inspection import PACPInspection
from ...records.nassco.inspection_technologies import InspectionTechnology
from ...records.nassco.lining_methods import LiningMethod
from ...records.nassco.pipe_uses import PipeUse
from ...records.nassco.survey_purposes import SurveyPurpose
from ...translations.base import Language
from ...translations.nassco.defect_codes import NasscoDefectTranslations
from ...translations.nassco.enum_translations import NasscoEnumTranslations
from ...utils.pdf_reports import generate_pdf
from ...utils.pipe_diagram_generator import generate_pipe_diagrams
from ...utils.pipe_ratings import calculate_pipe_ratings


def _ensure_header_is_dict(inspection_dict: dict) -> None:
    """
    Ensure the header in inspection_dict is converted to a dictionary.
    Preserves the section_string property if it exists.
    """
    if "header" in inspection_dict and hasattr(inspection_dict["header"], "__dict__"):
        section_string = inspection_dict["header"].section_string
        inspection_dict["header"] = inspection_dict["header"].__dict__.copy()
        inspection_dict["header"]["section_string"] = section_string


def generate_pacp_pdf(
    inspection: PACPInspection,
    pipe_diagrams_path: str,
    annotated_frames_dir: str,
    pdf_output_path: str,
    video_name: str,
    uom: Literal["metric", "imperial"],
    logo_path: Optional[str] = None,
    direction_img: Optional[str] = None,
    language: Language = Language.ENGLISH,
    address: Optional[str] = None,
    dual_logo: Optional[str] = None,
    pdf_title: Optional[str] = None,
):
    """
    Generate a PDF report for the PACP.

    Args:
        inspection: The PACP inspection to generate a PDF report for.
        pipe_diagrams_path: The path to the pipe diagrams.
        annotated_frames_dir: The path of the directory to the annotated frames.
        pdf_output_path: The path to the output PDF file.
        video_name: The name of the video.
        uom: The unit of measurement.
        logo_path: The path to the logo image.
        direction_img: The path to the direction image.
        language: The language for the report (default: English).
        address: The address for the report.
        dual_logo: The path to the dual logo image.
        pdf_title: The title for the PDF report.
    Returns:
        The path to the generated PDF file.
    """

    inspection_dict = inspection.__dict__.copy()
    inspection_dict["observations"].sort(key=lambda x: x.distance)

    # Convert nested objects to dictionaries for easier manipulation
    _ensure_header_is_dict(inspection_dict)

    if "survey" in inspection_dict.get("header", {}) and hasattr(inspection_dict["header"]["survey"], "__dict__"):
        inspection_dict["header"]["survey"] = inspection_dict["header"]["survey"].__dict__.copy()

    if "pipe" in inspection_dict.get("header", {}) and hasattr(inspection_dict["header"]["pipe"], "__dict__"):
        inspection_dict["header"]["pipe"] = inspection_dict["header"]["pipe"].__dict__.copy()

    # Process inspection technology - use label for enums, string as-is
    if hasattr(inspection.header.survey, "inspection_technology") and inspection.header.survey.inspection_technology:
        try:
            inspection_technology = InspectionTechnology.from_abbreviation(inspection.header.survey.inspection_technology).label
        except ValueError:
            inspection_technology = inspection.header.survey.inspection_technology

        inspection_dict["header"]["survey"]["inspection_technology"] = inspection_technology

    # Process pipe use - use label for enums, string as-is
    if hasattr(inspection.header.pipe, "pipe_use") and inspection.header.pipe.pipe_use:
        try:
            pipe_use = PipeUse.from_abbreviation(inspection.header.pipe.pipe_use).label
        except ValueError:
            pipe_use = inspection.header.pipe.pipe_use

        inspection_dict["header"]["pipe"]["pipe_use"] = pipe_use

    # Process lining method - use label for enums, string as-is
    if hasattr(inspection.header.pipe, "lining_method") and inspection.header.pipe.lining_method:
        try:
            lining_method = LiningMethod.from_abbreviation(inspection.header.pipe.lining_method).label
        except ValueError:
            lining_method = inspection.header.pipe.lining_method

        inspection_dict["header"]["pipe"]["lining_method"] = lining_method

    # Process purpose of survey - use label for enums, string as-is
    if hasattr(inspection.header.survey, "purpose_of_survey") and inspection.header.survey.purpose_of_survey:
        try:
            purpose_of_survey = SurveyPurpose.from_abbreviation(inspection.header.survey.purpose_of_survey).label
        except ValueError:
            purpose_of_survey = inspection.header.survey.purpose_of_survey

        inspection_dict["header"]["survey"]["purpose_of_survey"] = purpose_of_survey

    # Translate enum values to Spanish if needed
    if language == Language.SPANISH:
        # Translate survey direction
        if hasattr(inspection.header.survey, "direction_of_survey") and inspection.header.survey.direction_of_survey:
            inspection_dict["header"]["survey"]["direction_of_survey"] = NasscoEnumTranslations.translate_enum_object(inspection.header.survey.direction_of_survey, language)

        # Translate pipe material
        if hasattr(inspection.header.pipe, "material") and inspection.header.pipe.material:
            inspection_dict["header"]["pipe"]["material"] = NasscoEnumTranslations.translate_enum_object(inspection.header.pipe.material, language)

        # Translate lining method (already converted to label string earlier, so translate the string)
        if "pipe" in inspection_dict.get("header", {}) and "lining_method" in inspection_dict["header"].get("pipe", {}):
            lining_method_value = inspection_dict["header"]["pipe"]["lining_method"]
            if isinstance(lining_method_value, str):
                # Already converted to label string, translate it
                inspection_dict["header"]["pipe"]["lining_method"] = NasscoEnumTranslations.translate_enum_label(lining_method_value, language)
            elif hasattr(lining_method_value, "label"):
                # Still an enum object, translate it
                inspection_dict["header"]["pipe"]["lining_method"] = NasscoEnumTranslations.translate_enum_object(lining_method_value, language)

        # Translate weather
        if hasattr(inspection.header.survey, "weather") and inspection.header.survey.weather:
            inspection_dict["header"]["survey"]["weather"] = NasscoEnumTranslations.translate_enum_object(inspection.header.survey.weather, language)

        # Translate pre-cleaning
        if hasattr(inspection.header.survey, "pre_cleaning") and inspection.header.survey.pre_cleaning:
            inspection_dict["header"]["survey"]["pre_cleaning"] = NasscoEnumTranslations.translate_enum_object(inspection.header.survey.pre_cleaning, language)

        # Translate pipe use
        if hasattr(inspection.header.pipe, "pipe_use") and inspection.header.pipe.pipe_use:
            try:
                pipe_use = PipeUse.from_abbreviation(inspection.header.pipe.pipe_use)
                es_pipe_use = NasscoEnumTranslations.translate_enum_label(pipe_use.label, language)
            except ValueError:
                es_pipe_use = inspection.header.pipe.pipe_use
            inspection_dict["header"]["pipe"]["pipe_use"] = es_pipe_use

    # Compute page-wise observation batching: first 12, then 18 per page
    total_observations = len(inspection_dict.get("observations", []))
    first_page_count = PDFConfig.first_page_observation_count
    subsequent_page_count = PDFConfig.subsequent_page_observation_count

    # Build counts for each page based on total observations
    page_counts = []
    if total_observations > 0:
        first = min(first_page_count, total_observations)
        page_counts.append(first)
        remaining = total_observations - first
        while remaining > 0:
            take = min(subsequent_page_count, remaining)
            page_counts.append(take)
            remaining -= take

    # Build per-page mapping of diagram image to observation slice
    diagram_pages = []
    offset = 0
    diagram_paths = generate_pipe_diagrams(inspection, pipe_diagrams_path, show_text=False)
    for page_index, path in enumerate(diagram_paths):
        count_for_page = page_counts[page_index] if page_index < len(page_counts) else 0
        page_observations = inspection_dict["observations"][offset : offset + count_for_page] if count_for_page > 0 else []
        diagram_pages.append(
            {
                "page_index": page_index,
                "diagram_path": path.absolute().as_uri(),
                "start_index": offset,
                "end_index": (offset + len(page_observations) - 1) if page_observations else -1,
                "observations": page_observations,
            }
        )
        offset += count_for_page

    pipe_ratings = calculate_pipe_ratings(inspection, uom.lower() == "imperial")

    # Enrich inspection with annotated frame URIs and translate observations
    for index, observation in enumerate(inspection.observations):
        observation_dict = observation.__dict__.copy()
        annotated_frame_path = os.path.join(annotated_frames_dir, observation.image_reference)
        if not os.path.exists(annotated_frame_path):
            raise FileNotFoundError(f"Annotated frame file not found: {annotated_frame_path}")

        observation_dict["annotated_frame"] = Path(annotated_frame_path).absolute().as_uri()

        # Add the pdf_observation_string property to the dict (properties aren't in __dict__)
        observation_dict["pdf_observation_string"] = observation.pdf_observation_string

        # Translate observation data for Spanish
        if language == Language.SPANISH:
            # Translate the observation string
            observation_dict["pdf_observation_string"] = NasscoDefectTranslations.translate_observation_string(observation_dict["pdf_observation_string"], language)

            # Translate defect code label if it exists
            if "code" in observation_dict and observation_dict["code"] and hasattr(observation_dict["code"], "label"):
                # Translate the label using the defect translations
                translated_label = NasscoDefectTranslations.translate_observation_string(observation_dict["code"].label, language)

                # Create a simple object that mimics the enum structure
                class TranslatedCode:
                    def __init__(self, label, abbreviation):
                        self.label = label
                        self.abbreviation = abbreviation
                        self._abbreviation = abbreviation

                    def __str__(self):
                        return self.abbreviation

                    def __repr__(self):
                        return f"TranslatedCode({self.label} - {self.abbreviation})"

                observation_dict["code"] = TranslatedCode(translated_label, observation_dict["code"].abbreviation)

        inspection_dict["observations"][index] = observation_dict

    # Get language-specific measurement units
    if language == Language.SPANISH:
        measurement_units = "m" if uom == "metric" else "pies"
    else:
        measurement_units = "m" if uom == "metric" else "ft"

    pipe_size_units = "mm" if uom == "metric" else "in"

    data = {
        "title": pdf_title or "Reporte PACP" if language == Language.SPANISH else "PACP Report",
        "inspection": inspection_dict,
        "diagram_pages": diagram_pages,
        "logo_path": logo_path,
        "dual_logo_path": dual_logo,
        "video_name": video_name,
        "measurement_units": measurement_units,
        "pipe_ratings": pipe_ratings,
        "pipe_size_units": pipe_size_units,
        "direction_img": direction_img,
        "address": address,
    }

    pdf_path = generate_pdf("nassco/pacp/report.html", data, pdf_output_path, language)
    return pdf_path
