from datetime import date, time
from typing import Annotated, Any, Dict, Optional, Union

from pydantic import Field
from typing_extensions import Literal

from ....records.nassco.inspection_statuses import InspectionStatus
from ....records.wrc.directions import Direction
from ....records.wrc.drain_sewer_types import DrainSewerType
from ....records.wrc.drain_sewer_uses import DrainSewerUse
from ....records.wrc.flow_control_measures import FlowControlMeasure
from ....records.wrc.inspection_methods import InspectionMethod
from ....records.wrc.inspection_purposes import InspectionPurpose
from ....records.wrc.inspection_stages import InspectionStage
from ....records.wrc.land_ownership_types import LandOwnershipType
from ....records.wrc.lateral_inspection_start_points import LateralInspectionStartPoint
from ....records.wrc.lining_types import LiningType
from ....records.wrc.location_types import LocationTypeHighway, LocationTypeSewerage
from ....records.wrc.materials import Material
from ....records.wrc.photograph_image_formats import PhotographImageFormat
from ....records.wrc.pre_cleaned_options import PreCleanedOptions
from ....records.wrc.shapes import Shape
from ....records.wrc.temperature_options import Temperature
from ....records.wrc.video_image_formats import VideoImageFormat
from ....records.wrc.video_image_location_systems import VideoImageLocationSystem
from ....records.wrc.video_image_storage_media_options import VideoImageStorageMedia
from ....records.wrc.weather_options import WeatherOptions
from ....utils.xml_models import XMLModel


class SurveyInfo(XMLModel):
    client: Annotated[Optional[str], Field(default=None, description="Enter the name of the sewerage undertaker, highway authority, or other owner of the drain or sewer.")]
    name_of_surveyor: Annotated[Optional[str], Field(default=None, description="Enter the name of the person responsible for completing the survey.")]
    client_job_reference: Annotated[Optional[str], Field(default=None, description="Enter the client's job reference code.")]
    contractor_job_reference: Annotated[Optional[str], Field(default=None, description="Enter the contractor's job reference code.")]
    survey_date: Annotated[Optional[date], Field(default=None, description="Enter the survey date in the order day, month, year (YYYYMMDD).")]
    survey_time: Annotated[Optional[time], Field(default=None, description="Enter the time at the commencement of survey of drain/sewer length (24 hr format HH:MM).")]
    direction: Annotated[Optional[Direction], Field(default=None, description="Enter codes for the direction of survey in relation to flow (Codes for direction).")]
    pre_cleaned: Annotated[
        Optional[PreCleanedOptions],
        Field(
            default=None,
            description="Enter Y if pre-cleaning was carried out, N if it was not and Z if not known (Y or N shall only be entered if the information is definitely known).",
        ),
    ]
    critical_strategic_drain_sewer: Annotated[Optional[str], Field(default=None, description="Enter appropriate code for the survey purpose.")]
    purpose_of_inspection: Annotated[
        Optional[Union[InspectionPurpose, str]], Field(default=None, description="Enter an appropriate code for the survey purpose (Codes for purpose of inspection).")
    ]
    type_of_survey: Annotated[Optional[str], Field(default=None, description="Enter the type of survey (Codes for type of survey).")]
    inspection_stage: Annotated[Optional[InspectionStage], Field(default=None, description="Enter the code for inspection stage (Codes for inspection stage).")]
    flow_control_measures: Annotated[
        Optional[FlowControlMeasure],
        Field(default=None, description="Enter the measures taken to deal with the flow at the time of the inspection (Codes for flow control measures)."),
    ]
    weather: Annotated[Optional[WeatherOptions], Field(default=None, description="Enter an appropriate code for weather (Codes for weather).")]
    temperature: Annotated[Optional[Temperature], Field(default=None, description="Enter the temperature in Celsius or as a code (Codes for Temperature).")]
    method_of_inspection: Annotated[
        Optional[Union[InspectionMethod, str]], Field(default=None, description="Enter the codes for the method of inspection (Codes for method of inspection).")
    ]
    video_image_storage_media: Annotated[
        Optional[VideoImageStorageMedia], Field(default=None, description="Enter the type of media used for recording images (Codes for video images storage media).")
    ]
    video_image_location_system: Annotated[
        Optional[VideoImageLocationSystem],
        Field(default=None, description="For moving images, the method of recording the position on the video media should be recorded (Codes for Video Image Location System)."),
    ]
    video_image_format: Annotated[Optional[VideoImageFormat], Field(default=None, description="Enter the type of media used for storing videos (Codes for video image format).")]
    video_image_file_name: Annotated[Optional[str], Field(default=None, description="Enter the name of the file as stored.")]
    video_volume_reference: Annotated[Optional[str], Field(default=None, description="Enter the reference number of the volume.")]
    photograph_image_format: Annotated[Optional[PhotographImageFormat], Field(default=None, description="Enter the type of media used for storing images.")]
    photograph_volume_reference: Annotated[Optional[str], Field(default=None, description="Enter the reference number of the film.")]
    wrc_certified: Annotated[Optional[str], Field(default=None, description="Enter the WRC certified number.")]
    full_segment_cctv: Annotated[Optional[Literal["Yes", "No"]], Field(default=None, description="Enter the full segment CCTV.")]
    project_reference_number: Annotated[Optional[str], Field(default=None, description="Enter the project reference number.")]
    inspection_status: Annotated[Optional[InspectionStatus], Field(default=None, description="Status of inspection")]

    class Config:
        element_name_map = {
            "name_of_surveyor": "Surveyed_by",
            "survey_date": "Date",
            "survey_time": "Time",
            "direction": "Direction_of_Survey",
            "pre_cleaned": "Pre_Cleaning",
            "strategic_drains_sewer": "Sewer_Category",
            "type_of_survey": "Survey_Type",
            "purpose_of_inspection": "Purpose_of_Survey",
            "client": "Customer",
            "client_job_reference": "Work_Order_Number",
            "contractor_job_reference": "PO_Number",
            "weather": "Weather",
            "flow_control_measures": "Flow_Control",
            "inspection_stage": "Inspection_Status",
            "method_of_inspection": "Inspection_Technology_Used",
            "survey_type": "Survey_Type",
            "video_image_file_name": "Media_Label",
            "full_segment_cctv": "Full_Segment_CCTV",
        }
        ignore_fields = [
            "critical_strategic_drain_sewer",
            "temperature",
            "video_image_storage_media",
            "video_image_location_system",
            "video_image_format",
            "video_volume_reference",
            "photograph_image_format",
            "photograph_volume_reference",
            "wrc_certified",
            "project_reference_number",
        ]


class Location(XMLModel):
    drainage_area: Annotated[Optional[str], Field(default=None, description="When provided by the client, this should be entered here.")]
    division_district: Annotated[Optional[str], Field(default=None, description="Enter an appropriate district/division code.")]
    location_street_name: Annotated[Optional[str], Field(default=None, description="Enter the street name through which the drain/sewer is situated.")]
    location_town_or_village: Annotated[Optional[str], Field(default=None, description="Enter a town, village or area where the drain/sewer is located.")]
    location_type_code: Annotated[
        Optional[LocationTypeSewerage | LocationTypeHighway], Field(default=None, description="Enter the appropriate code for the location of the drain/sewer.")
    ]
    location_details: Annotated[Optional[str], Field(default=None, description="Location Details of the access point or manhole")]
    land_ownership: Annotated[Optional[LandOwnershipType], Field(default=None, description="Enter the owner of the land on which the survey is carried out.")]

    class Config:
        element_name_map = {
            "drainage_area": "Drainage_Area",
            "location_street_name": "Street",
            "location_town_or_village": "City",
            "location_type_code": "Location_Code",
            "location_details": "Location_Details",
        }
        ignore_fields = ["land_ownership", "division_district"]


class PipeInfo(XMLModel):
    pipeline_length_reference: Annotated[Optional[str], Field(default=None, description="Enter the pipeline length reference derived from the upstream manhole of each survey.")]
    lateral_inspection_start_point: Annotated[
        Optional[LateralInspectionStartPoint], Field(default=None, description="Enter the start point of the inspection (Codes for Lateral inspection start point).")
    ]
    longitudinal_location_of_start_of_lateral: Annotated[
        Optional[str], Field(default=None, description="Enter the length in metres of the start point of the lateral survey from Node 1.")
    ]
    circumferential_location_of_start_of_lateral: Annotated[
        Optional[str], Field(default=None, description="Enter the clock reference of the connection of the lateral being surveyed.")
    ]
    use_of_drain_sewer: Annotated[Optional[DrainSewerUse], Field(default=None, description="Enter codes to define the use of the drain/sewer (Codes for Drain/Sewer).")]
    type_of_drain_sewer: Annotated[Optional[DrainSewerType], Field(default=None, description="Enter codes for the type of drain/sewer (Codes for types of drain/sewer).")]
    height_or_diameter: Annotated[Optional[str], Field(default=None, description="Enter the dimensions of either drain/sewer height or diameter (If circular, in millimetres).")]
    width: Annotated[Optional[str], Field(default=None, description="Enter the maximum drain/sewer width.")]
    shape: Annotated[Optional[Shape], Field(default=None, description="Enter the codes to describe the drain/sewer shape (Codes for Shapes).")]
    material: Annotated[Optional[Material], Field(default=None, description="Enter codes for the drain/sewer material (Codes for Material).")]
    lining_material: Annotated[
        Optional[Material], Field(default=None, description="Where the drain/sewer has been relined, enter codes listed under 'Material' to define the lining material.")
    ]
    lining_type: Annotated[Optional[LiningType], Field(default=None, description="Where a drain/sewer has been lined, record the method of the lining (Codes for Lining type).")]
    pipe_unit_length: Annotated[
        Optional[float],
        Field(
            default=None,
            description=(
                "Enter the length in metres of normal pipe "
                "(Be careful not to measure the rocker pipe adjacent to a manhole as it is normal to use only part of a pipe unit in this location)."
            ),
        ),
    ]
    expected_length: Annotated[
        Optional[float],
        Field(
            default=None,
            description=(
                "Enter the distance once the survey is complete, where the whole length is surveyed, "
                "between the exit of the start manhole and the entrance to the finish manhole (Note that this length is not the length between manhole centrelines. "
                "The accuracy required is generally ±1 or ±0.3 m, whichever is the greater)."
            ),
        ),
    ]
    year_constructed: Annotated[Optional[int], Field(default=None, description="Enter the actual year the drain/sewer was constructed (Enter Z if the year is not known).")]
    standard: Annotated[Optional[str], Field(default="WRc 5.0", description="Enter the version of the standard used to record the data.")]
    grease_found: Annotated[Optional[str], Field(default=None, description="Enter the grease found (N/M/H).")]
    upstream_water_level: Annotated[Optional[str], Field(default=None, description="Enter the upstream water level.")]
    downstream_water_level: Annotated[Optional[str], Field(default=None, description="Enter the downstream water level.")]

    class Config:
        element_name_map = {
            "pipeline_length_reference": "Pipe_Segment_Reference",
            "use_of_drain_sewer": "Pipe_Use",
            "height_or_diameter": "Height",
            "width": "Width",
            "shape": "Shape",
            "expected_length": "Length_Surveyed",
            "year_constructed": "Year_Contstructed",
            "standard": "Manual_Version",
            "grease_found": "Grease",
            "upstream_water_level": "Upstream_Water_Level",
            "downstream_water_level": "Downstream_Water_Level",
        }
        ignore_fields = [
            "material",
            "lateral_inspection_start_point",
            "longitudinal_location_of_start_of_lateral",
            "circumferential_location_of_start_of_lateral",
            "type_of_drain_sewer",
            "lining_material",
            "pipe_unit_length",
            "lining_type",
        ]

    def get_extra_fields(self) -> Dict[str, Any]:
        return {
            "Material": self.material.abbreviation if self.material else None,
        }


class Measurements(XMLModel):
    start_node_reference: Annotated[Optional[str], Field(default=None, description="Enter reference in accordance with manhole no.")]
    start_node_coordinate: Annotated[Optional[str], Field(default=None, description="If the client specifies the use of a coordinate, then enter the XY coordinates.")]
    start_node_depth: Annotated[
        Optional[str], Field(default=None, description="Enter in metres the distance between the cover level of the manhole and the invert level of the pipe/sewer being surveyed.")
    ]
    finish_node_reference: Annotated[Optional[str], Field(default=None, description="Enter a reference in accordance with the manhole/node.")]
    finish_node_depth: Annotated[Optional[str], Field(default=None, description="Enter the depth of the finish manhole in the same way as the start manhole.")]
    finish_node_coordinate: Annotated[Optional[str], Field(default=None, description="If the client specifies the use of a coordinate, then enter the XY coordinates.")]

    class Config:
        element_name_map = {
            "start_node_reference": "Upstream_MH_No",
            "finish_node_reference": "Downstream_MH_No",
            "start_node_coordinate": "Upstream_MH_Northing",
            "finish_node_coordinate": "Downstream_MH_Northing",
        }
        ignore_fields = [
            "start_node_depth",
            "finish_node_depth",
        ]


class MainlineInspectionHeaders(XMLModel):
    survey: Annotated[SurveyInfo, Field(default_factory=SurveyInfo, description="Survey Info")]
    location: Annotated[Location, Field(default_factory=Location, description="Location Info")]
    pipe: Annotated[PipeInfo, Field(default_factory=PipeInfo, description="Pipe Info")]
    measurements: Annotated[Measurements, Field(default_factory=Measurements, description="Measurements Info")]
    additional_info: Annotated[Optional[str], Field(default=None, description="Any other appropriate details about the survey")]

    class Config:
        attribute_fields = []
        element_name_map = {
            "survey": "General",
            "location": "Location",
            "pipe": "Pipe",
            "measurements": "Measurements",
            "additional_info": "Additional_Info",
        }

    @property
    def section_string(self) -> str:
        """
        Returns the section string for the inspection.
        """

        if self.survey.direction:
            if self.survey.direction == Direction.upstream:
                return f"{self.measurements.finish_node_reference} --- {self.measurements.start_node_reference}"
            elif self.survey.direction == Direction.downstream:
                return f"{self.measurements.start_node_reference} --- {self.measurements.finish_node_reference}"
        return ""
