"""Spatial validation for QGIS layers (geometry types, CRS)."""

from __future__ import annotations

from typing import TYPE_CHECKING

from qgis.core import (
    QgsMapLayer,
    QgsProject,
    QgsRasterLayer,
    QgsVectorLayer,
    QgsWkbTypes,
)

from sec_interp.core.domain import FieldType

from .field_validator import validate_field_exists, validate_field_type

if TYPE_CHECKING:
    from sec_interp.core.validation.validation_helpers import ValidationContext


def validate_layer_exists(
    layer_name: str | None,
) -> tuple[bool, str, QgsMapLayer | None]:
    """Validate that a layer with the given name exists in the current QGIS project.

    Args:
        layer_name: The name of the layer to search for.

    Returns:
        tuple: (is_valid, error_message, layer)
            - is_valid: True if at least one matching layer was found.
            - error_message: Error details if no layer was found.
            - layer: The first matching layer instance if valid, else None.

    """
    if not layer_name:
        return False, "Layer name is required", None

    # Prefer lookup by ID if it's a valid ID, otherwise search by name
    layer = QgsProject.instance().mapLayer(layer_name)
    if not layer:
        # Fallback to name search if not an ID
        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == layer_name:
                layer = lyr
                break

    if not layer:
        return False, f"Layer '{layer_name}' not found in project", None

    if not layer.isValid():
        return False, f"Layer '{layer_name}' is not valid", None

    return True, "", layer


def validate_layer_has_features(layer: QgsVectorLayer) -> tuple[bool, str]:
    """Validate that a vector layer contains at least one feature.

    Args:
        layer: The QGIS vector layer to check.

    Returns:
        tuple: (is_valid, error_message)
            - is_valid: True if the layer has features.
            - error_message: Error details if the layer is empty.

    """
    if not layer:
        return False, "Layer is None"

    if not isinstance(layer, QgsVectorLayer):
        return False, "Layer is not a vector layer"

    if layer.featureCount() == 0:
        return False, f"Layer '{layer.name()}' has no features"

    return True, ""


def validate_layer_geometry(
    layer: QgsVectorLayer, expected_type: QgsWkbTypes.GeometryType
) -> tuple[bool, str]:
    """Validate that a vector layer matches the expected QGIS geometry type.

    Args:
        layer: The QGIS vector layer to check.
        expected_type: The required QgsWkbTypes.GeometryType.

    Returns:
        tuple: (is_valid, error_message)
            - is_valid: True if the geometry type matches.
            - error_message: Detailed error if types mismatch.

    """
    if not layer:
        return False, "Layer is None"

    if not isinstance(layer, QgsVectorLayer):
        return False, "Layer is not a vector layer"

    actual_type = QgsWkbTypes.geometryType(layer.wkbType())

    if actual_type != expected_type:
        type_names = {
            QgsWkbTypes.PointGeometry: "Point",
            QgsWkbTypes.LineGeometry: "Line",
            QgsWkbTypes.PolygonGeometry: "Polygon",
            QgsWkbTypes.UnknownGeometry: "Unknown",
            QgsWkbTypes.NullGeometry: "Null",
        }
        expected_name = type_names.get(expected_type, f"Type {expected_type}")
        actual_name = type_names.get(actual_type, f"Type {actual_type}")

        return False, (
            f"Geometry type mismatch for layer '{layer.name()}': "
            f"Found {actual_name}, but expected {expected_name}. "
            f"Please select a valid {expected_name.lower()} layer."
        )

    return True, ""


def validate_raster_band(layer: QgsRasterLayer, band_number: int) -> tuple[bool, str]:
    """Validate that a specified band number exists in the given raster layer.

    Args:
        layer: The QGIS raster layer to check.
        band_number: The 1-based index of the raster band.

    Returns:
        tuple: (is_valid, error_message)
            - is_valid: True if the band exists.
            - error_message: Error message if the band is out of range.

    """
    if not layer:
        return False, "Layer is None"

    if not isinstance(layer, QgsRasterLayer):
        return False, "Layer is not a raster layer"

    band_count = layer.bandCount()

    if band_number < 1 or band_number > band_count:
        return False, (
            f"Band number {band_number} is invalid. Layer '{layer.name()}' has {band_count} band(s)"
        )

    return True, ""


def validate_structural_requirements(
    layer: QgsVectorLayer,
    layer_name: str,
    dip_field: str | None,
    strike_field: str | None,
    context: ValidationContext | None = None,
) -> tuple[bool, str]:
    """Validate structural layer requirements (geometry and attribute fields)."""
    is_valid, msg = _check_struct_layer_validity(layer, layer_name)
    if not is_valid:
        if context:
            context.add_error(msg)
        return False, msg

    # Validate individual fields
    for field, label in [(dip_field, "Dip"), (strike_field, "Strike")]:
        if field:
            is_valid, msg = _validate_struct_field(layer, field, label)
            if not is_valid:
                if context:
                    context.add_error(msg)
                return False, msg

    return True, ""


def _check_struct_layer_validity(layer: QgsVectorLayer, layer_name: str) -> tuple[bool, str]:
    """Check if the structural layer is valid and has correct geometry."""
    if not layer.isValid():
        return False, f"Structural layer '{layer_name}' is not valid."

    if QgsWkbTypes.geometryType(layer.wkbType()) != QgsWkbTypes.PointGeometry:
        return False, "Structural layer must be a point layer."
    return True, ""


def validate_geology_requirements(
    layer: QgsVectorLayer,
    field_name: str | None,
    context: ValidationContext | None = None,
) -> tuple[bool, str]:
    """Validate geology layer requirements (polygons and attribute field)."""
    is_valid, error = _check_geology_layer_validity(layer)
    if not is_valid:
        if context:
            context.add_error(error, "geology_layer")
        return False, error

    # Check field
    if not field_name:
        msg = "Geology unit field is required when geology layer is selected"
        if context:
            context.add_error(msg, "geology_field")
        return False, msg

    is_valid, error = validate_field_exists(layer, field_name)
    if not is_valid:
        if context:
            context.add_error(error, "geology_field")
        return False, error

    return True, ""


def _check_geology_layer_validity(layer: QgsVectorLayer) -> tuple[bool, str]:
    """Check if the geology layer is valid and has correct geometry."""
    if not layer.isValid():
        return False, f"Geology layer '{layer.name()}' is not valid."

    is_valid, error = validate_layer_geometry(layer, QgsWkbTypes.PolygonGeometry)
    if not is_valid:
        return False, error

    is_valid, error = validate_layer_has_features(layer)
    if not is_valid:
        return False, error
    return True, ""


def _validate_struct_field(layer: QgsVectorLayer, field_name: str, label: str) -> tuple[bool, str]:
    """Validate a specific structural field existance and type."""
    is_valid, msg = validate_field_exists(layer, field_name)
    if not is_valid:
        return False, msg

    is_valid, msg = validate_field_type(
        layer,
        field_name,
        [FieldType.INT, FieldType.DOUBLE, FieldType.LONG_LONG, FieldType.STRING],
    )
    if not is_valid:
        return False, f"{label} field error: {msg}"

    return True, ""


def validate_crs_compatibility(layers: list[QgsMapLayer]) -> tuple[bool, str]:
    """Validate that a list of layers have compatible Coordinate Reference Systems.

    If layers have different CRSs, it returns a warning message instead of an error.
    """
    valid_layers = [L for L in layers if L and L.isValid()]
    if not valid_layers:
        return True, ""

    ref_layer = valid_layers[0]
    ref_crs = ref_layer.crs()

    incompatible = [
        f"  - {L.name()}: {L.crs().authid()}" for L in valid_layers if L.crs() != ref_crs
    ]

    if incompatible:
        warning = (
            f"⚠ CRS mismatch detected!\n\n"
            f"Reference CRS: {ref_crs.authid()} ({ref_layer.name()})\n"
            f"Incompatible layers:\n" + "\n".join(incompatible) + "\n\n"
            "QGIS will reproject on-the-fly, but this may affect accuracy.\n"
        )
        return False, warning

    return True, ""
