from __future__ import annotations

import io
import os
from pathlib import Path
from typing import Any, Literal

from pygef.bore import BoreData
from pygef.broxml.parse_bore import read_bore as read_bore_xml
from pygef.broxml.parse_cpt import read_cpt as read_cpt_xml
from pygef.common import Location, VerticalDatumClass
from pygef.cpt import CPTData
from pygef.gef.parse_bore import _GefBore
from pygef.gef.parse_cpt import _GefCpt

GEF_ID = "#GEFID"


def is_gef_file(file: io.BytesIO | Path | str) -> bool:
    """
    gef files start with '#GEFID' so we check the content
    of the file
    """
    if isinstance(file, io.BytesIO):
        pos = file.tell()
        is_gef = file.read(6).decode().startswith(GEF_ID)
        file.seek(pos)
        return is_gef
    if os.path.exists(file):
        with open(file, errors="ignore") as f:
            return f.read(6).startswith(GEF_ID)
    if isinstance(file, str):
        return file[:6].startswith(GEF_ID)
    raise FileNotFoundError("Could not find the GEF file.")


def read_bore(
    file: io.BytesIO | Path | str,
    index: int = 0,
    engine: Literal["auto", "gef", "xml"] = "auto",
) -> BoreData:
    """
    Parse the bore file. Can either be BytesIO, Path or str

    :param file: bore file
    :param index: only valid for xml files
    :param engine: default is "auto". parsing engine.
        Please note that auto engine checks if the files starts with `#GEFID`.
    """
    if engine == "gef" or is_gef_file(file) and engine == "auto":
        if index > 0:
            raise ValueError("an index > 0 not supported for GEF files")
        if isinstance(file, io.BytesIO):
            return gef_bore_to_bore_data(_GefBore(string=file.read().decode()))
        if os.path.exists(file):
            return gef_bore_to_bore_data(_GefBore(path=file))
        else:
            return gef_bore_to_bore_data(_GefBore(string=file))
    return read_bore_xml(file)[index]


def read_cpt(
    file: io.BytesIO | Path | str,
    index: int = 0,
    engine: Literal["auto", "gef", "xml"] = "auto",
) -> CPTData:
    """
    Parse the cpt file. Can either be BytesIO, Path or str

    :param file: bore file
    :param index: only valid for xml files
    :param engine: default is "auto". parsing engine.
        Please note that auto engine checks if the files starts with `#GEFID`.
    """

    if engine == "gef" or is_gef_file(file) and engine == "auto":
        if index > 0:
            raise ValueError("an index > 0 not supported for GEF files")
        if isinstance(file, io.BytesIO):
            return gef_cpt_to_cpt_data(_GefCpt(string=file.read().decode()))
        if os.path.exists(file):
            return gef_cpt_to_cpt_data(_GefCpt(path=file))
        else:
            return gef_cpt_to_cpt_data(_GefCpt(string=file))
    return read_cpt_xml(file)[index]


def convert_height_system_to_vertical_datum(height_system: float) -> str:
    if height_system == 31000.0:
        return "nap"
    else:
        return str(int(height_system))


def gef_cpt_to_cpt_data(gef_cpt: _GefCpt) -> CPTData:
    kwargs: dict[str, Any] = {}

    kwargs["delivered_location"] = Location(
        # all gef files are RD new
        srs_name="urn:ogc:def:crs:EPSG::28992",
        x=gef_cpt.x,
        y=gef_cpt.y,
    )
    kwargs["standardized_location"] = None
    kwargs["bro_id"] = None
    kwargs["alias"] = gef_cpt.test_id
    kwargs["data"] = gef_cpt.df
    kwargs["research_report_date"] = gef_cpt.file_date
    kwargs["cpt_standard"] = None
    kwargs["groundwater_level"] = gef_cpt.groundwater_level
    kwargs["dissipationtest_performed"] = None
    kwargs["quality_class"] = gef_cpt.cpt_class
    kwargs["predrilled_depth"] = gef_cpt.pre_excavated_depth
    kwargs["final_depth"] = gef_cpt.end_depth_of_penetration_test
    kwargs["cpt_description"] = ""
    kwargs["cpt_type"] = gef_cpt.type_of_cone_penetration_test
    kwargs["cone_surface_area"] = gef_cpt.nom_surface_area_cone_tip
    kwargs["cone_diameter"] = None
    kwargs["cone_surface_quotient"] = gef_cpt.net_surface_area_quotient_of_the_cone_tip
    kwargs["cone_to_friction_sleeve_distance"] = (
        gef_cpt.distance_between_cone_and_centre_of_friction_casing
    )
    kwargs["cone_to_friction_sleeve_surface_area"] = None
    kwargs["cone_to_friction_sleeve_surface_quotient"] = (
        gef_cpt.net_surface_area_quotient_of_the_friction_casing
    )

    kwargs["zlm_cone_resistance_before"] = (
        gef_cpt.zero_measurement_cone_before_penetration_test
    )
    kwargs["zlm_cone_resistance_after"] = (
        gef_cpt.zero_measurement_cone_after_penetration_test
    )
    kwargs["zlm_inclination_ew_before"] = (
        gef_cpt.zero_measurement_inclination_ew_before_penetration_test
    )
    kwargs["zlm_inclination_ew_after"] = (
        gef_cpt.zero_measurement_inclination_ew_after_penetration_test
    )
    kwargs["zlm_inclination_ns_before"] = (
        gef_cpt.zero_measurement_inclination_ns_before_penetration_test
    )
    kwargs["zlm_inclination_ns_after"] = (
        gef_cpt.zero_measurement_inclination_ns_after_penetration_test
    )
    kwargs["zlm_inclination_resultant_before"] = None
    kwargs["zlm_inclination_resultant_after"] = None
    kwargs["zlm_local_friction_before"] = (
        gef_cpt.zero_measurement_friction_before_penetration_test
    )
    kwargs["zlm_local_friction_after"] = (
        gef_cpt.zero_measurement_friction_after_penetration_test
    )
    kwargs["zlm_pore_pressure_u1_before"] = (
        gef_cpt.zero_measurement_ppt_u1_before_penetration_test
    )
    kwargs["zlm_pore_pressure_u2_before"] = (
        gef_cpt.zero_measurement_ppt_u2_before_penetration_test
    )
    kwargs["zlm_pore_pressure_u3_before"] = (
        gef_cpt.zero_measurement_ppt_u3_before_penetration_test
    )
    kwargs["zlm_pore_pressure_u1_after"] = (
        gef_cpt.zero_measurement_ppt_u1_after_penetration_test
    )
    kwargs["zlm_pore_pressure_u2_after"] = (
        gef_cpt.zero_measurement_ppt_u2_after_penetration_test
    )
    kwargs["zlm_pore_pressure_u3_after"] = (
        gef_cpt.zero_measurement_ppt_u3_after_penetration_test
    )
    kwargs["delivered_vertical_position_offset"] = gef_cpt.zid
    kwargs["delivered_vertical_position_datum"] = VerticalDatumClass(
        str(int(gef_cpt.height_system))
    )

    # TODO! parse measurementtext 9 in gef?
    kwargs["delivered_vertical_position_reference_point"] = "unknown"

    return CPTData(**kwargs)


def gef_bore_to_bore_data(gef_bore: _GefBore) -> BoreData:
    kwargs: dict[str, Any] = {}

    kwargs["delivered_location"] = Location(
        # all gef files are RD new
        srs_name="urn:ogc:def:crs:EPSG::28992",
        x=gef_bore.x,
        y=gef_bore.y,
    )
    kwargs["standardized_location"] = None
    kwargs["bro_id"] = None
    kwargs["alias"] = gef_bore.test_id
    kwargs["groundwater_level"] = None
    kwargs["research_report_date"] = gef_bore.file_date
    kwargs["description_procedure"] = "unknown"
    kwargs["delivered_vertical_position_offset"] = gef_bore.zid
    kwargs["delivered_vertical_position_datum"] = "unknown"
    kwargs["delivered_vertical_position_reference_point"] = "unknown"
    kwargs["bore_rock_reached"] = None
    kwargs["final_bore_depth"] = None
    kwargs["final_sample_depth"] = None
    kwargs["bore_hole_completed"] = None
    kwargs["data"] = gef_bore.df
    return BoreData(**kwargs)
