"""Profile export façade.

Specific exporters for profile data (Shapefiles).
"""

from __future__ import annotations

import math
from pathlib import Path
from typing import Any

from qgis.core import (
    QgsFeature,
    QgsField,
    QgsFields,
    QgsGeometry,
    QgsPointXY,
)
from qgis.PyQt.QtCore import QMetaType

from sec_interp.core import utils as scu
from sec_interp.logger_config import get_logger

from .base_exporter import BaseExporter

logger = get_logger(__name__)

# Constants for profile geometry validation
MIN_REQUIRED_POINTS = 2


class ProfileLineShpExporter(BaseExporter):
    """Exports the topographic profile line to a Shapefile."""

    def get_supported_extensions(self) -> list[str]:
        """Get supported file extensions.

        Returns:
            List of supported extensions.

        """
        return [".shp"]

    def export(self, output_path: Path, data: dict[str, Any]) -> bool:
        """Export the topographic profile line to a Shapefile.

        Args:
            output_path: Path to the output Shapefile.
            data: Dictionary containing 'profile_data' and 'crs'.

        """
        profile_data = data.get("profile_data")
        crs = data.get("crs")
        if not profile_data or not crs:
            return False

        try:
            points = [QgsPointXY(d, e) for d, e in profile_data]
            geom = QgsGeometry.fromPolylineXY(points)
            if not geom or geom.isNull():
                return False

            fields = QgsFields()
            fields.append(QgsField("id", QMetaType.Type.Int))

            writer = scu.create_shapefile_writer(str(output_path), crs, fields)

            feat = QgsFeature()
            feat.setGeometry(geom)
            feat.setAttributes([1])
            writer.addFeature(feat)
        except Exception:
            logger.exception(f"Failed to export profile line to {output_path}")
            return False
        else:
            return True


class GeologyShpExporter(BaseExporter):
    """Exports the geological profile to a Shapefile."""

    def get_supported_extensions(self) -> list[str]:
        """Get supported file extensions.

        Returns:
            List of supported extensions.

        """
        return [".shp"]

    def export(self, output_path: Path, data: dict[str, Any]) -> bool:
        """Export the geological profile to a Shapefile.

        Args:
            output_path: Path to the output Shapefile.
            data: Dictionary containing 'geology_data' and 'crs'.

        """
        geology_data = data.get("geology_data")
        crs = data.get("crs")

        if not geology_data or not crs:
            return False

        try:
            fields = self._create_geology_fields(geology_data)
            writer = scu.create_shapefile_writer(str(output_path), crs, fields)

            self._write_geology_features(writer, geology_data, fields)
            del writer

        except Exception:
            logger.exception(f"Failed to export geology profile to {output_path}")
            return False
        else:
            return True

    def _write_geology_features(self, writer: Any, geology_data: list, fields: QgsFields) -> None:
        """Write geology segments to the writer.

        Args:
            writer: The vector file writer.
            geology_data: List of geology segments.
            fields: The QGIS field collection.

        """
        for segment in geology_data:
            feat = self._create_geology_feature(segment, fields)
            if feat:
                writer.addFeature(feat)

    def _create_geology_fields(self, geology_data: list) -> QgsFields:
        """Create fields from the first segment's attributes."""
        fields = QgsFields()
        if geology_data:
            # Use attributes from first segment as template
            first_attrs = geology_data[0].attributes
            for key in first_attrs:
                fields.append(QgsField(key, QMetaType.Type.QString))
        return fields

    def _create_geology_feature(self, segment: Any, fields: QgsFields) -> QgsFeature | None:
        """Create a feature for a geology segment."""
        if len(segment.points) < MIN_REQUIRED_POINTS:
            return None

        points = [QgsPointXY(d, e) for d, e in segment.points]
        geom = QgsGeometry.fromPolylineXY(points)

        feat = QgsFeature(fields)
        feat.setGeometry(geom)

        # Set attributes
        for key, val in segment.attributes.items():
            idx = fields.indexOf(key)
            if idx >= 0:
                feat.setAttribute(idx, val)
        return feat


class StructureShpExporter(BaseExporter):
    """Exports the structural profile to a Shapefile."""

    def get_supported_extensions(self) -> list[str]:
        """Get supported file extensions.

        Returns:
            List of supported extensions.

        """
        return [".shp"]

    def export(self, output_path: Path, data: dict[str, Any]) -> bool:
        """Export the structural profile to a Shapefile.

        Args:
            output_path: Path to the output Shapefile.
            data: Dictionary containing 'structural_data', 'crs', 'dip_scale_factor',
                and 'raster_res'.

        """
        structural_data = data.get("structural_data")
        crs = data.get("crs")
        dip_scale_factor = data.get("dip_scale_factor", 4)
        raster_res = data.get("raster_res", 1.0)

        if not structural_data or not crs:
            return False

        try:
            line_length = raster_res * dip_scale_factor
            fields = self._create_structure_fields(structural_data)
            writer = scu.create_shapefile_writer(str(output_path), crs, fields)

            self._write_structure_features(writer, structural_data, fields, line_length)
            del writer
        except Exception:
            logger.exception(f"Failed to export structural profile to {output_path}")
            return False
        else:
            return True

    def _write_structure_features(
        self, writer: Any, structural_data: list, fields: QgsFields, line_length: float
    ) -> None:
        """Write structural measurements to the writer.

        Args:
            writer: The vector file writer.
            structural_data: List of structural measurements.
            fields: The QGIS field collection.
            line_length: Length of the dip line in plot units.

        """
        for m in structural_data:
            feat = self._create_structure_feature(m, fields, line_length)
            if feat:
                writer.addFeature(feat)

    def _create_structure_fields(self, structural_data: list) -> QgsFields:
        """Create fields for structural data."""
        fields = QgsFields()
        if structural_data:
            first_attrs = structural_data[0].attributes
            for key in first_attrs:
                fields.append(QgsField(key, QMetaType.Type.QString))

        fields.append(QgsField("app_dip", QMetaType.Type.Double))
        fields.append(QgsField("dist", QMetaType.Type.Double))
        fields.append(QgsField("elev", QMetaType.Type.Double))
        return fields

    def _create_structure_feature(
        self, m: Any, fields: QgsFields, line_length: float
    ) -> QgsFeature:
        """Create a feature for a structural measurement."""
        geom = self._calculate_dip_geometry(m, line_length)

        feat = QgsFeature(fields)
        feat.setGeometry(geom)

        # Set attributes
        for key, val in m.attributes.items():
            idx = fields.indexOf(key)
            if idx >= 0:
                feat.setAttribute(idx, val)

        feat["app_dip"] = m.apparent_dip
        feat["dist"] = m.distance
        feat["elev"] = m.elevation
        return feat

    def _calculate_dip_geometry(self, m: Any, line_length: float) -> QgsGeometry:
        """Calculate the line geometry for a structural dip."""
        rad_dip = math.radians(m.apparent_dip)
        dy = -line_length * math.sin(abs(rad_dip))
        dx = line_length * math.cos(abs(rad_dip))
        if m.apparent_dip < 0:
            dx = -dx

        p1 = QgsPointXY(m.distance, m.elevation)
        p2 = QgsPointXY(m.distance + dx, m.elevation + dy)
        return QgsGeometry.fromPolylineXY([p1, p2])


class AxesShpExporter(BaseExporter):
    """Exports the profile axes to a Shapefile."""

    def get_supported_extensions(self) -> list[str]:
        """Get supported file extensions.

        Returns:
            List of supported extensions.

        """
        return [".shp"]

    def export(self, output_path: Path, data: dict[str, Any]) -> bool:
        """Export the profile axes to a Shapefile.

        Args:
            output_path: Path to the output Shapefile.
            data: Dictionary containing 'profile_data' and 'crs'.

        """
        profile_data = data.get("profile_data")
        crs = data.get("crs")
        if not profile_data or not crs:
            return False

        try:
            dists = [p[0] for p in profile_data]
            elevs = [p[1] for p in profile_data]

            min_d, max_d = min(dists), max(dists)
            min_e, max_e = min(elevs), max(elevs)

            if max_d == min_d:
                max_d = min_d + 100
            if max_e == min_e:
                max_e = min_e + 10

            e_range = max_e - min_e
            min_e_padded = min_e - e_range * 0.05
            max_e_padded = max_e + e_range * 0.05

            lines = [
                [QgsPointXY(min_d, min_e_padded), QgsPointXY(min_d, max_e_padded)],
                [QgsPointXY(max_d, min_e_padded), QgsPointXY(max_d, max_e_padded)],
                [QgsPointXY(min_d, min_e_padded), QgsPointXY(max_d, min_e_padded)],
            ]

            fields = QgsFields()
            fields.append(QgsField("axis", QMetaType.Type.QString))

            writer = scu.create_shapefile_writer(str(output_path), crs, fields)

            axis_names = ["Left", "Right", "Bottom"]
            self._write_axes_features(writer, lines, axis_names)
            del writer

        except Exception:
            logger.exception(f"Failed to export axes to {output_path}")
            return False
        else:
            return True

    def _write_axes_features(self, writer: Any, lines: list, axis_names: list[str]) -> None:
        """Write axes lines to the writer.

        Args:
            writer: The vector file writer.
            lines: List of coordinate pairs for each axis.
            axis_names: List of axis labels.

        """
        for i, points in enumerate(lines):
            feat = QgsFeature()
            geom = QgsGeometry.fromPolylineXY(points)
            feat.setGeometry(geom)
            feat.setAttributes([axis_names[i]])
            writer.addFeature(feat)
