from pathlib import Path
from dataclasses import dataclass
from typing import Protocol
from qgis.PyQt.QtWidgets import QMessageBox
from osgeo import gdal
from osgeo.osr import SpatialReference


@dataclass
class RasterMetadataData:
    """
    Represents raster metadata.

    Attributes:
        width (int): raster number of columns.
        height (int): raster number of rows.
        xmin (float): The minimum x-coordinate of the raster.
        ymin (float): The minimum y-coordinate of the raster.
        cell_size (float): raster cell size.
        nodata_value (int | None): Raster nodata value , or None if not specified.
    """

    width: int
    height: int
    xmin: float
    ymin: float
    cell_size: float
    nodata_value: int | None
    crs: str = "EPSG:2154"


class FileParserStrategy(Protocol):
    """
    A strategy for parsing a file and returning its raster metadata informations.

    Methods:
    --------
    get_raster_metadata(file_path: Path) -> RasterMetadataData | None:
        Analyzes the given file and returns the raster metadata extracted from this file, or None if the file
        does not contain metadata.
    """

    def get_raster_metadata(self, file_path: Path) -> RasterMetadataData | None: ...


class TxtFileParser:
    """A class for parsing raster metadata from a txt file.

    Attributes:
        None

    Methods:
        get_raster_metadata(file_path: Path) -> RasterMetadataData | None:
            Parses the raster metadata from the given txt file.

    """

    def get_raster_metadata(self, file_path: Path) -> RasterMetadataData | None:
        r"""Logic for parsing a txt file
         when reading the file it should have this format :
        #parameter file generated with APILand
        #Fri Nov 10 17:06:17 CET 2023
        miny=6824050.142000002
        height=1538
        minx=356062.7354999971
        cellsize=10.0
        noDataValue=-1
        width=1404
        maxy=6839430.142000002
        maxx=370102.7354999971
        crs=EPSG\:32740"""

        # initialize the variables
        width: int = 0
        height: int = 0
        xmin: float = 0.0
        ymin: float = 0.0
        cell_size: float = 0.0
        nodata_value: int | None = None
        crs: str = ""

        with open(str(file_path), "r", encoding="utf-8") as infile:
            lines = infile.readlines()
        if not lines:
            return None

        for raw_line in lines:
            line = raw_line.strip()
            if not line or line.startswith("#"):
                continue
            parts = line.split("=", 1)
            if len(parts) != 2:
                continue
            key, value = parts[0].strip(), parts[1].strip()
            try:
                if key == "width":
                    width = int(value)
                elif key == "height":
                    height = int(value)
                elif key == "minx":
                    xmin = float(value)
                elif key == "miny":
                    ymin = float(value)
                elif key == "cellsize":
                    cell_size = float(value)
                elif key == "noDataValue":
                    nodata_value = int(value)
                elif key == "crs":
                    crs = value.replace("\\", "")
            except ValueError:
                QMessageBox.critical(
                    None,
                    "Error",
                    f"Could not convert value {value} for {key} to the expected type",
                )
                return None

        raster_metadata: RasterMetadataData = RasterMetadataData(
            width=width,
            height=height,
            xmin=xmin,
            ymin=ymin,
            cell_size=cell_size,
            nodata_value=nodata_value,
        )
        if crs:
            raster_metadata.crs = crs

        return raster_metadata


class RasterFileParser:
    """
    A class for parsing raster files and extracting metadata.

    Attributes:
        None

    Methods:
        get_raster_metadata: Extracts metadata from a raster file.
    """

    def get_raster_metadata(self, file_path: Path) -> RasterMetadataData | None:
        """
        Retrieves metadata of a raster file.

        Args:
            file_path (Path): The path to the raster file.

        Returns:
            RasterMetadataData | None: The raster metadata if successfully parsed, None otherwise.
        """
        # Logic for parsing a raster file
        if not file_path.exists():
            return None
        dataset = gdal.Open(str(file_path))  # DataSet
        if dataset is None:
            return None

        try:
            width = int(dataset.RasterXSize)
            height = int(dataset.RasterYSize)
            xmin = float(dataset.GetGeoTransform()[0])
            ymin = float(dataset.GetGeoTransform()[3]) + height * float(
                dataset.GetGeoTransform()[5]
            )
            cell_size = int(dataset.GetGeoTransform()[1])
            nodata_value: int | None = dataset.GetRasterBand(1).GetNoDataValue()
            crs: SpatialReference = dataset.GetSpatialRef()

            raster_metadata: RasterMetadataData = RasterMetadataData(
                width=width,
                height=height,
                xmin=xmin,
                ymin=ymin,
                cell_size=cell_size,
                nodata_value=nodata_value,
            )
            if crs is not None:
                raster_metadata.crs = (
                    f"{crs.GetAuthorityName(None)}:{crs.GetAuthorityCode(None)}"
                )

            return raster_metadata
        except ValueError as e:
            # log the message to a QMessage box and explicitly tell for what element there is a conversion error
            QMessageBox.critical(
                None,
                "Error",
                f"Could not convert value {e} to the expected type",
            )
            return None
