from typing import Any, Dict, Union

from ....records.nassco.nassco_defects import NasscoDefectRecords


class MACPValidationRules:
    """Validation rules for MACP observations based on NASSCO requirements."""

    @staticmethod
    def _get_field_value(values: Union[Dict[str, Any], Any], field_name: str) -> Any:
        """Helper method to get field value from either dict or model instance."""
        if hasattr(values, field_name):
            return getattr(values, field_name)
        else:
            return values.get(field_name)

    @staticmethod
    def validate_observation(values: Union[Dict[str, Any], Any]) -> None:
        """
        Validate all fields based on NASSCO MACP requirements.
        This method implements comprehensive validation rules for each defect code.
        """
        code = MACPValidationRules._get_field_value(values, "code")
        if not code:
            return

        code = NasscoDefectRecords.from_abbreviation(code)

        # Validate based on defect code requirements
        MACPValidationRules._validate_access_point_defects(values, code)
        MACPValidationRules._validate_crack_fracture_defects(values, code)
        MACPValidationRules._validate_broken_hole_defects(values, code)
        MACPValidationRules._validate_deformed_defects(values, code)
        MACPValidationRules._validate_collapse_defects(values, code)
        MACPValidationRules._validate_joint_defects(values, code)
        MACPValidationRules._validate_surface_damage_defects(values, code)
        MACPValidationRules._validate_lining_feature_defects(values, code)
        MACPValidationRules._validate_weld_failure_defects(values, code)
        MACPValidationRules._validate_point_repair_defects(values, code)
        MACPValidationRules._validate_brick_defects(values, code)
        MACPValidationRules._validate_grout_defects(values, code)
        MACPValidationRules._validate_roots_defects(values, code)
        MACPValidationRules._validate_infiltration_defects(values, code)
        MACPValidationRules._validate_intruding_defects(values, code)
        MACPValidationRules._validate_vermin_defects(values, code)
        MACPValidationRules._validate_miscellaneous_defects(values, code)
        MACPValidationRules._validate_deposits_defects(values, code)
        MACPValidationRules._validate_obstruction_defects(values, code)
        MACPValidationRules._validate_roots_tap_defects(values, code)
        MACPValidationRules._validate_missing_mortar_defects(values, code)

    @staticmethod
    def _validate_access_point_defects(values: Union[Dict[str, Any], Any], code: NasscoDefectRecords) -> None:
        """Validate access point defects."""
        access_point_codes = [
            NasscoDefectRecords.access_point_catch_basin,
            NasscoDefectRecords.access_point_cleanout_house,
            NasscoDefectRecords.access_point_cleanout_mainline,
            NasscoDefectRecords.access_point_cleanout_property_line,
            NasscoDefectRecords.access_point_discharge_point,
            NasscoDefectRecords.access_point_end_of_pipe,
            NasscoDefectRecords.access_point_junction_box,
            NasscoDefectRecords.access_point_meter,
            NasscoDefectRecords.access_point_manhole,
            NasscoDefectRecords.access_point_other_special_structure,
            NasscoDefectRecords.access_point_tee_connection,
            NasscoDefectRecords.access_point_wastewater_access_device,
            NasscoDefectRecords.access_point_wet_well,
        ]

        if code in access_point_codes:
            distance = MACPValidationRules._get_field_value(values, "distance")
            remarks = MACPValidationRules._get_field_value(values, "remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")

            # Remarks required for specific access point defects
            remarks_required = [
                NasscoDefectRecords.access_point_cleanout_mainline,
                NasscoDefectRecords.access_point_junction_box,
                NasscoDefectRecords.access_point_meter,
                NasscoDefectRecords.access_point_manhole,
                NasscoDefectRecords.access_point_wastewater_access_device,
            ]
            if code in remarks_required and remarks is None:
                required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_crack_fracture_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate crack and fracture defects."""
        crack_fracture_codes = [
            NasscoDefectRecords.crack_longitudinal,
            NasscoDefectRecords.crack_circumferential,
            NasscoDefectRecords.crack_multiple,
            NasscoDefectRecords.crack_spiral,
            NasscoDefectRecords.crack_hinge_2,
            NasscoDefectRecords.crack_hinge_3,
            NasscoDefectRecords.crack_hinge_4,
            NasscoDefectRecords.fracture_longitudinal,
            NasscoDefectRecords.fracture_circumferential,
            NasscoDefectRecords.fracture_multiple,
            NasscoDefectRecords.fracture_spiral,
            NasscoDefectRecords.fracture_hinge_2,
            NasscoDefectRecords.fracture_hinge_3,
            NasscoDefectRecords.fracture_hinge_4,
        ]

        if code in crack_fracture_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            circumferential_location_to = values.get("circumferential_location_to")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            # Circumferential location to required for most crack/fracture defects
            circumferential_to_required = [
                NasscoDefectRecords.crack_circumferential,
                NasscoDefectRecords.crack_hinge_2,
                NasscoDefectRecords.crack_hinge_3,
                NasscoDefectRecords.crack_hinge_4,
                NasscoDefectRecords.crack_multiple,
                NasscoDefectRecords.crack_spiral,
                NasscoDefectRecords.fracture_circumferential,
                NasscoDefectRecords.fracture_hinge_2,
                NasscoDefectRecords.fracture_hinge_3,
                NasscoDefectRecords.fracture_hinge_4,
                NasscoDefectRecords.fracture_multiple,
                NasscoDefectRecords.fracture_spiral,
            ]
            if code in circumferential_to_required and circumferential_location_to is None:
                required_fields.append("circumferential_location_to")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_broken_hole_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate broken and hole defects."""
        broken_hole_codes = [
            NasscoDefectRecords.broken,
            NasscoDefectRecords.broken_soil_visible,
            NasscoDefectRecords.broken_void_visible,
            NasscoDefectRecords.hole,
            NasscoDefectRecords.hole_soil_visible,
            NasscoDefectRecords.hole_void_visible,
        ]

        if code in broken_hole_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

            # Joint and step are optional for broken/hole defects according to CSV
            # Only validate if they are provided

    @staticmethod
    def _validate_deformed_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate deformed defects."""
        deformed_codes = [
            NasscoDefectRecords.deformed_flexible_bulging_inverse_curvature,
            NasscoDefectRecords.deformed_flexible_bulging_round,
            NasscoDefectRecords.deformed_flexible_creasing,
            NasscoDefectRecords.deformed_flexible_elliptical,
            NasscoDefectRecords.deformed_brick_bulging_inverse_curvature,
            NasscoDefectRecords.deformed_brick_bulging_round,
            NasscoDefectRecords.deformed_rigid,
        ]

        if code in deformed_codes:
            distance = values.get("distance")
            component = values.get("component")
            value_defect_percent = values.get("value_defect_percent")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")
            elif value_defect_percent is not None and value_defect_percent > 40:
                raise ValueError(f"Defect percentage must be ≤ 40% for {code.full_name}")

    @staticmethod
    def _validate_collapse_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate collapse defects."""
        if code == NasscoDefectRecords.collapse:
            distance = values.get("distance")
            component = values.get("component")
            value_defect_percent = values.get("value_defect_percent")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")
            elif value_defect_percent is not None and value_defect_percent < 45:
                raise ValueError("Defect percentage must be ≥ 45% for collapse")

    @staticmethod
    def _validate_joint_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate joint defects."""
        joint_codes = [
            NasscoDefectRecords.joint_offset_small,
            NasscoDefectRecords.joint_offset_small_defective,
            NasscoDefectRecords.joint_offset_medium,
            NasscoDefectRecords.joint_offset_medium_defective,
            NasscoDefectRecords.joint_offset_large,
            NasscoDefectRecords.joint_offset_large_defective,
            NasscoDefectRecords.joint_separated_small,
            NasscoDefectRecords.joint_separated_medium,
            NasscoDefectRecords.joint_separated_large,
            NasscoDefectRecords.joint_angular_small,
            NasscoDefectRecords.joint_angular_medium,
            NasscoDefectRecords.joint_angular_large,
        ]

        if code in joint_codes:
            distance = values.get("distance")
            component = values.get("component")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_surface_damage_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate surface damage defects."""
        surface_damage_codes = [
            NasscoDefectRecords.surface_damage_roughness_increased,
            NasscoDefectRecords.surface_damage_aggregate_visible,
            NasscoDefectRecords.surface_damage_aggregate_projecting,
            NasscoDefectRecords.surface_damage_aggregate_missing,
            NasscoDefectRecords.surface_damage_reinforcement_visible,
            NasscoDefectRecords.surface_damage_reinforcement_projecting,
            NasscoDefectRecords.surface_damage_reinforcement_corroded,
            NasscoDefectRecords.surface_damage_missing_wall,
            NasscoDefectRecords.surface_damage_surface_spalling,
            NasscoDefectRecords.surface_damage_spalling_of_coating,
            NasscoDefectRecords.surface_damage_corrosion,
            NasscoDefectRecords.surface_damage_other,
        ]

        if code in surface_damage_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if code in [NasscoDefectRecords.surface_damage_other]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_lining_feature_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate lining feature defects."""
        lining_feature_codes = [
            NasscoDefectRecords.lining_feature_abandoned_connection,
            NasscoDefectRecords.lining_feature_annular_space,
            NasscoDefectRecords.lining_feature_blistered,
            NasscoDefectRecords.lining_feature_service_cut_shifted,
            NasscoDefectRecords.lining_feature_detached,
            NasscoDefectRecords.lining_feature_discoloration,
            NasscoDefectRecords.lining_feature_defective_end,
            NasscoDefectRecords.lining_feature_delamination,
            NasscoDefectRecords.lining_feature_overcut_service,
            NasscoDefectRecords.lining_feature_resin_slug,
            NasscoDefectRecords.lining_feature_undercut_service,
            NasscoDefectRecords.lining_feature_wrinkled,
            NasscoDefectRecords.lining_feature_other,
        ]

        if code in lining_feature_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if code in [NasscoDefectRecords.lining_feature_other]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_weld_failure_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate weld failure defects."""
        weld_failure_codes = [
            NasscoDefectRecords.weld_failure_circumferential,
            NasscoDefectRecords.weld_failure_longitudinal,
            NasscoDefectRecords.weld_failure_multiple,
            NasscoDefectRecords.weld_failure_spiral,
            NasscoDefectRecords.weld_failure_other,
        ]

        if code in weld_failure_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if code in [NasscoDefectRecords.weld_failure_other]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_point_repair_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate point repair defects."""
        point_repair_codes = [
            NasscoDefectRecords.point_repair_liner,
            NasscoDefectRecords.point_repair_liner_defective,
            NasscoDefectRecords.point_repair_patch,
            NasscoDefectRecords.point_repair_patch_defective,
            NasscoDefectRecords.point_repair_replacement,
            NasscoDefectRecords.point_repair_replacement_defective,
            NasscoDefectRecords.point_repair_other,
            NasscoDefectRecords.point_repair_other_defective,
        ]

        if code in point_repair_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if remarks is None:
                required_fields.append("remarks")

            # Circumferential location required for patch defects
            if code in [NasscoDefectRecords.point_repair_patch, NasscoDefectRecords.point_repair_patch_defective]:
                if circumferential_location_from is None:
                    required_fields.append("circumferential_location_from")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_brick_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate brick-related defects."""
        brick_codes = [
            NasscoDefectRecords.displaced_brick,
            NasscoDefectRecords.missing_brick,
        ]

        if code in brick_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

        # Dropped invert
        if code == NasscoDefectRecords.dropped_invert:
            distance = values.get("distance")
            component = values.get("component")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_grout_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate grout-related defects."""
        grout_codes = [
            NasscoDefectRecords.grout_at_location,
            NasscoDefectRecords.grout_test_fail_joint,
            NasscoDefectRecords.grout_test_fail_lateral,
            NasscoDefectRecords.grout_test_pass_joint,
            NasscoDefectRecords.grout_test_pass_lateral,
            NasscoDefectRecords.grout_test_unable_to_test_joint,
            NasscoDefectRecords.grout_test_unable_to_test_lateral,
        ]

        if code in grout_codes:
            distance = values.get("distance")
            component = values.get("component")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if code in [NasscoDefectRecords.grout_test_unable_to_test_joint, NasscoDefectRecords.grout_test_unable_to_test_lateral]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_roots_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate roots-related defects."""
        roots_codes = [
            NasscoDefectRecords.roots_fine_barrel,
            NasscoDefectRecords.roots_fine_lateral,
            NasscoDefectRecords.roots_fine_connection,
            NasscoDefectRecords.roots_fine_joint,
            NasscoDefectRecords.roots_medium_barrel,
            NasscoDefectRecords.roots_medium_lateral,
            NasscoDefectRecords.roots_medium_connection,
            NasscoDefectRecords.roots_medium_joint,
            NasscoDefectRecords.roots_ball_barrel,
            NasscoDefectRecords.roots_ball_lateral,
            NasscoDefectRecords.roots_ball_connection,
            NasscoDefectRecords.roots_ball_joint,
        ]

        if code in roots_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            joint = values.get("joint")
            value_defect_percent = values.get("value_defect_percent")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            # Joint required for joint-specific roots defects
            joint_required = [
                NasscoDefectRecords.roots_fine_joint,
                NasscoDefectRecords.roots_medium_joint,
                NasscoDefectRecords.roots_ball_joint,
            ]
            if code in joint_required and joint is None:
                required_fields.append("joint")

            # Percentage validation
            if code in [
                NasscoDefectRecords.roots_ball_barrel,
                NasscoDefectRecords.roots_ball_lateral,
                NasscoDefectRecords.roots_ball_connection,
                NasscoDefectRecords.roots_ball_joint,
            ]:
                if value_defect_percent is None:
                    required_fields.append("value_defect_percent")
                elif value_defect_percent is not None and value_defect_percent < 55:
                    raise ValueError(f"Defect percentage must be ≥ 55% for {code.full_name}")

            if code in [
                NasscoDefectRecords.roots_medium_barrel,
                NasscoDefectRecords.roots_medium_lateral,
                NasscoDefectRecords.roots_medium_connection,
                NasscoDefectRecords.roots_medium_joint,
            ]:
                if value_defect_percent is None:
                    required_fields.append("value_defect_percent")
                elif value_defect_percent is not None and value_defect_percent > 50:
                    raise ValueError(f"Defect percentage must be ≤ 50% for {code.full_name}")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_infiltration_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate infiltration defects."""
        infiltration_codes = [
            NasscoDefectRecords.infiltration_weeper,
            NasscoDefectRecords.infiltration_weeper_barrel,
            NasscoDefectRecords.infiltration_weeper_lateral,
            NasscoDefectRecords.infiltration_weeper_connection,
            NasscoDefectRecords.infiltration_weeper_joint,
            NasscoDefectRecords.infiltration_dripper,
            NasscoDefectRecords.infiltration_dripper_barrel,
            NasscoDefectRecords.infiltration_dripper_lateral,
            NasscoDefectRecords.infiltration_dripper_connection,
            NasscoDefectRecords.infiltration_dripper_joint,
            NasscoDefectRecords.infiltration_runner,
            NasscoDefectRecords.infiltration_runner_barrel,
            NasscoDefectRecords.infiltration_runner_lateral,
            NasscoDefectRecords.infiltration_runner_connection,
            NasscoDefectRecords.infiltration_runner_joint,
            NasscoDefectRecords.infiltration_gusher,
            NasscoDefectRecords.infiltration_gusher_barrel,
            NasscoDefectRecords.infiltration_gusher_lateral,
            NasscoDefectRecords.infiltration_gusher_connection,
            NasscoDefectRecords.infiltration_gusher_joint,
            NasscoDefectRecords.infiltration_stain,
            NasscoDefectRecords.infiltration_stain_barrel,
            NasscoDefectRecords.infiltration_stain_lateral,
            NasscoDefectRecords.infiltration_stain_connection,
            NasscoDefectRecords.infiltration_stain_joint,
        ]

        if code in infiltration_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            joint = values.get("joint")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            # Joint required for joint-specific infiltration defects
            joint_required = [
                NasscoDefectRecords.infiltration_weeper_joint,
                NasscoDefectRecords.infiltration_dripper_joint,
                NasscoDefectRecords.infiltration_runner_joint,
                NasscoDefectRecords.infiltration_gusher_joint,
                NasscoDefectRecords.infiltration_stain_joint,
            ]
            if code in joint_required and joint is None:
                required_fields.append("joint")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_intruding_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate intruding defects."""
        intruding_codes = [
            NasscoDefectRecords.intruding_sealing_material_sealing_ring,
            NasscoDefectRecords.intruding_seal_material_sealing_ring_hanging,
            NasscoDefectRecords.intruding_seal_material_sealing_ring_broken,
            NasscoDefectRecords.intruding_seal_material_sealing_ring_loose,
            NasscoDefectRecords.intruding_sealing_material_grout,
            NasscoDefectRecords.intruding_sealing_material_other,
        ]

        if code in intruding_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            circumferential_location_to = values.get("circumferential_location_to")
            value_defect_percent = values.get("value_defect_percent")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if circumferential_location_to is None:
                required_fields.append("circumferential_location_to")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")

            # Remarks required for intruding other
            if code in [NasscoDefectRecords.intruding_sealing_material_other]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_vermin_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate vermin defects."""
        vermin_codes = [
            NasscoDefectRecords.vermin_rat,
            NasscoDefectRecords.vermin_cockroach,
            NasscoDefectRecords.vermin_other,
        ]

        if code in vermin_codes:
            distance = values.get("distance")
            component = values.get("component")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")

            # Remarks required for vermin other
            if code in [NasscoDefectRecords.vermin_other]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_miscellaneous_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate miscellaneous defects."""
        misc_codes = [
            NasscoDefectRecords.miscellaneous_general_observation,
            NasscoDefectRecords.miscellaneous_general_photograph,
            NasscoDefectRecords.miscellaneous_pipe_joint_length_change,
            NasscoDefectRecords.miscellaneous_lining_change,
            NasscoDefectRecords.miscellaneous_material_change,
            NasscoDefectRecords.miscellaneous_survey_abandoned,
            NasscoDefectRecords.miscellaneous_shape_size_change,
            NasscoDefectRecords.miscellaneous_water_mark,
            NasscoDefectRecords.miscellaneous_dye_test_not_visible,
            NasscoDefectRecords.miscellaneous_dye_test_visible,
            NasscoDefectRecords.miscellaneous_water_level_sag,
            NasscoDefectRecords.miscellaneous_water_level,
        ]

        if code in misc_codes:
            distance = values.get("distance")
            component = values.get("component")
            remarks = values.get("remarks")
            value_1st_dimension = values.get("value_1st_dimension")
            value_defect_percent = values.get("value_defect_percent")
            required_fields = []

            if distance is None:
                required_fields.append("distance")

            # Component required for most miscellaneous defects
            component_required = [
                NasscoDefectRecords.miscellaneous_general_observation,
                NasscoDefectRecords.miscellaneous_general_photograph,
                NasscoDefectRecords.miscellaneous_lining_change,
                NasscoDefectRecords.miscellaneous_material_change,
                NasscoDefectRecords.miscellaneous_shape_size_change,
                NasscoDefectRecords.miscellaneous_water_mark,
            ]
            if code in component_required and component is None:
                required_fields.append("component")

            # Remarks required for specific miscellaneous defects
            remarks_required = [
                NasscoDefectRecords.miscellaneous_general_observation,
                NasscoDefectRecords.miscellaneous_survey_abandoned,
                NasscoDefectRecords.miscellaneous_material_change,
                NasscoDefectRecords.miscellaneous_lining_change,
                NasscoDefectRecords.miscellaneous_dye_test_visible,
            ]
            if code in remarks_required and remarks is None:
                required_fields.append("remarks")

            # Specific requirements
            if code in [NasscoDefectRecords.miscellaneous_pipe_joint_length_change, NasscoDefectRecords.miscellaneous_shape_size_change]:
                if value_1st_dimension is None:
                    required_fields.append("value_1st_dimension")

            if code in [NasscoDefectRecords.miscellaneous_water_mark, NasscoDefectRecords.miscellaneous_water_level_sag, NasscoDefectRecords.miscellaneous_water_level]:
                if value_defect_percent is None:
                    required_fields.append("value_defect_percent")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_deposits_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate deposits defects."""
        deposits_codes = [
            NasscoDefectRecords.deposits_attached_encrustation,
            NasscoDefectRecords.deposits_attached_grease,
            NasscoDefectRecords.deposits_attached_ragging,
            NasscoDefectRecords.deposits_attached_other,
            NasscoDefectRecords.deposits_settled_hard_compacted,
            NasscoDefectRecords.deposits_settled_fine,
            NasscoDefectRecords.deposits_settled_gravel,
            NasscoDefectRecords.deposits_settled_other,
            NasscoDefectRecords.deposits_ingress_fine,
            NasscoDefectRecords.deposits_ingress_gravel,
            NasscoDefectRecords.deposits_ingress_other,
        ]

        if code in deposits_codes:
            distance = values.get("distance")
            component = values.get("component")
            value_defect_percent = values.get("value_defect_percent")
            circumferential_location_from = values.get("circumferential_location_from")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")

            # Remarks required for "other" deposits defects
            other_deposits = [
                NasscoDefectRecords.deposits_attached_other,
                NasscoDefectRecords.deposits_settled_other,
                NasscoDefectRecords.deposits_ingress_other,
            ]
            if code in other_deposits and remarks is None:
                required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_obstruction_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate obstruction defects."""
        obstruction_codes = [
            NasscoDefectRecords.obstruction_brick_masonry,
            NasscoDefectRecords.obstruction_pipe_material_in_invert,
            NasscoDefectRecords.obstruction_intruding_through_wall,
            NasscoDefectRecords.obstruction_wedged_in_joint,
            NasscoDefectRecords.obstruction_through_connection,
            NasscoDefectRecords.obstruction_external_pipe_or_cable,
            NasscoDefectRecords.obstruction_built_into_structure,
            NasscoDefectRecords.obstruction_construction_debris,
            NasscoDefectRecords.obstruction_rocks,
            NasscoDefectRecords.obstruction_other_objects,
        ]

        if code in obstruction_codes:
            distance = values.get("distance")
            component = values.get("component")
            value_defect_percent = values.get("value_defect_percent")
            circumferential_location_from = values.get("circumferential_location_from")
            joint = values.get("joint")
            remarks = values.get("remarks")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            # Joint required for obstruction wedged in joint
            if code in [NasscoDefectRecords.obstruction_wedged_in_joint]:
                if joint is None:
                    required_fields.append("joint")

            # Remarks required for obstruction other objects
            if code in [NasscoDefectRecords.obstruction_other_objects]:
                if remarks is None:
                    required_fields.append("remarks")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_roots_tap_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate roots tap defects with thickness requirements."""
        roots_tap_codes = [
            NasscoDefectRecords.roots_tap_barrel,
            NasscoDefectRecords.roots_tap_connection,
            NasscoDefectRecords.roots_tap_lateral,
            NasscoDefectRecords.roots_tap_joint,
        ]

        if code in roots_tap_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            value_defect_percent = values.get("value_defect_percent")
            joint = values.get("joint")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")
            if value_defect_percent is None:
                required_fields.append("value_defect_percent")
            if code in [NasscoDefectRecords.roots_tap_joint]:
                if joint is None:
                    required_fields.append("joint")
            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")

    @staticmethod
    def _validate_missing_mortar_defects(values: Dict[str, Any], code: NasscoDefectRecords) -> None:
        """Validate missing mortar defects."""
        missing_mortar_codes = [
            NasscoDefectRecords.missing_mortar_small,
            NasscoDefectRecords.missing_mortar_medium,
            NasscoDefectRecords.missing_mortar_large,
        ]

        if code in missing_mortar_codes:
            distance = values.get("distance")
            component = values.get("component")
            circumferential_location_from = values.get("circumferential_location_from")
            required_fields = []

            if distance is None:
                required_fields.append("distance")
            if component is None:
                required_fields.append("component")
            if circumferential_location_from is None:
                required_fields.append("circumferential_location_from")

            if required_fields:
                raise ValueError(f"{', '.join(required_fields)} is required for {code.full_name}")
