"""
Layer Exporter

v4.0 EPIC-1 Phase E1: Extracted from filter_task.py export methods

Handles layer export operations to various formats.

IMPORTANT: Export is INDEPENDENT from "exploring" and QGIS selection.
====================================================================

Export behavior:
- Uses QgsVectorFileWriter which respects the layer's subsetString (filter)
- WITH subset: exports only features matching the filter
- WITHOUT subset: exports all features in the layer
- Does NOT use QGIS selectedFeatures() - selection is ignored
- Does NOT reference current_layer from exploring tab

Original source: modules/tasks/filter_task.py lines 9551-10400 (~850 lines)
"""

import os
import logging
from enum import Enum
from dataclasses import dataclass
from typing import Optional, List, Any

try:
    from qgis.core import (
        QgsVectorLayer,
        QgsVectorFileWriter,
        QgsCoordinateReferenceSystem,
        QgsProject,
    )
    from qgis.PyQt.QtCore import QCoreApplication
    from qgis import processing
    QGIS_AVAILABLE = True
except ImportError:
    QGIS_AVAILABLE = False
    QgsVectorLayer = Any
    QgsVectorFileWriter = Any
    QgsCoordinateReferenceSystem = Any
    QgsProject = Any

logger = logging.getLogger('FilterMate.Export')

# Canonical mapping of uppercased OGR driver descriptions to file extensions.
# Covers both short aliases (SHP, TAB) and full OGR names (ESRI SHAPEFILE, MAPINFO FILE).
# Used by LayerExporter, BatchExporter, and ExportHandler for consistent extension resolution.
OGR_EXTENSION_MAP = {
    'GPKG': '.gpkg',
    'SHP': '.shp',
    'SHAPEFILE': '.shp',
    'ESRI SHAPEFILE': '.shp',
    'GEOJSON': '.geojson',
    'JSON': '.geojson',
    'GML': '.gml',
    'KML': '.kml',
    'LIBKML': '.kml',
    'CSV': '.csv',
    'XLSX': '.xlsx',
    'TAB': '.tab',
    'MAPINFO': '.tab',
    'MAPINFO FILE': '.tab',
    'DXF': '.dxf',
    'SQLITE': '.sqlite',
    'SPATIALITE': '.spatialite',
    'FLATGEOBUF': '.fgb',
    'ESRI FILEGDB': '.gdb',
    'OPENFILEGDB': '.gdb',
    'PGDUMP': '.sql',
}


def get_extension_for_format(datatype: str) -> str:
    """Return the file extension (with dot) for an OGR driver description.

    Handles both short aliases ('SHP') and full OGR names ('ESRI Shapefile').
    Falls back to '.{datatype_lower}' for unknown drivers.
    """
    ext = OGR_EXTENSION_MAP.get(datatype.upper())
    if ext:
        return ext
    # Fallback: use lowercased datatype as extension (only safe for single-word names)
    clean = datatype.strip().lower().replace(' ', '_')
    return f'.{clean}'


class ExportFormat(Enum):
    """Supported export formats."""
    GPKG = "GPKG"
    SHAPEFILE = "ESRI Shapefile"
    GEOJSON = "GeoJSON"
    GML = "GML"
    KML = "KML"
    CSV = "CSV"
    XLSX = "XLSX"
    TAB = "MapInfo File"
    DXF = "DXF"
    SPATIALITE = "SpatiaLite"


@dataclass
class ExportConfig:
    """Configuration for layer export."""

    layers: List[str]
    """Layer names to export."""

    output_path: str
    """Output path (file or directory)."""

    datatype: str
    """Export format (GPKG, SHP, etc.)."""

    projection: Optional[QgsCoordinateReferenceSystem] = None
    """Target CRS or None to use layer's CRS."""

    style_format: Optional[str] = None
    """Style format (qml, sld, lyrx) or None."""

    save_styles: bool = False
    """Whether to save layer styles."""

    batch_mode: bool = False
    """Whether to export each layer to separate file."""

    batch_zip: bool = False
    """Whether to create zip archive."""


@dataclass
class ExportResult:
    """Result of export operation."""

    success: bool
    """Whether export succeeded."""

    exported_count: int = 0
    """Number of layers successfully exported."""

    failed_count: int = 0
    """Number of layers that failed to export."""

    output_path: Optional[str] = None
    """Path to exported file/directory."""

    error_message: Optional[str] = None
    """Error message if export failed."""

    warnings: List[str] = None
    """Non-fatal warnings during export."""

    def __post_init__(self):
        if self.warnings is None:
            self.warnings = []


class LayerExporter:
    """
    Exports QGIS vector layers to various formats.

    Supports:
    - Single layer export
    - Batch export to directory
    - GeoPackage multi-layer export
    - Style export (via StyleExporter)
    - CRS reprojection

    Example:
        exporter = LayerExporter(project)
        config = ExportConfig(
            layers=["layer1", "layer2"],
            output_path="/path/to/output.gpkg",
            datatype="GPKG",
            save_styles=True
        )
        result = exporter.export(config)
        if result.success:

    """

    # Format driver mapping (uppercased key → OGR driver name)
    DRIVER_MAP = {
        'GPKG': 'GPKG',
        'SHP': 'ESRI Shapefile',
        'SHAPEFILE': 'ESRI Shapefile',
        'ESRI SHAPEFILE': 'ESRI Shapefile',
        'GEOJSON': 'GeoJSON',
        'JSON': 'GeoJSON',
        'GML': 'GML',
        'KML': 'KML',
        'LIBKML': 'LIBKML',
        'CSV': 'CSV',
        'XLSX': 'XLSX',
        'TAB': 'MapInfo File',
        'MAPINFO': 'MapInfo File',
        'MAPINFO FILE': 'MapInfo File',
        'DXF': 'DXF',
        'SQLITE': 'SQLite',
        'SPATIALITE': 'SpatiaLite',
        'FLATGEOBUF': 'FlatGeobuf',
    }

    def __init__(self, project: Optional[QgsProject] = None):
        """
        Initialize layer exporter.

        Args:
            project: QgsProject instance or None to use current project
        """
        self.project = project or (QgsProject.instance() if QGIS_AVAILABLE else None)

    def export(self, config: ExportConfig) -> ExportResult:
        """
        Export layers according to configuration.

        Args:
            config: Export configuration

        Returns:
            ExportResult with export status and statistics
        """
        if not QGIS_AVAILABLE:
            return ExportResult(
                success=False,
                error_message="QGIS not available"
            )

        if not self.project:
            return ExportResult(
                success=False,
                error_message="No QGIS project available"
            )

        # Special handling for GPKG multi-layer export
        if config.datatype.upper() == 'GPKG' and not config.batch_mode:
            return self.export_to_gpkg(config.layers, config.output_path, config.save_styles)

        # Batch export (one file per layer)
        if config.batch_mode:
            return self.export_batch(config)

        # Single layer export
        if len(config.layers) == 1:
            return self.export_single_layer(
                config.layers[0],
                config.output_path,
                config.projection,
                config.datatype,
                config.style_format,
                config.save_styles
            )

        # Multiple layers to directory
        return self.export_multiple_to_directory(config)

    def export_single_layer(
        self,
        layer_name: str,
        output_path: str,
        projection: Optional[QgsCoordinateReferenceSystem],
        datatype: str,
        style_format: Optional[str],
        save_styles: bool
    ) -> ExportResult:
        """
        Export a single layer to file.

        Args:
            layer_name: Layer name to export
            output_path: Output file path
            projection: Target CRS or None to use layer's CRS
            datatype: Export format (e.g., 'SHP', 'GPKG')
            style_format: Style file format or None
            save_styles: Whether to save layer styles

        Returns:
            ExportResult with export status
        """
        # Get layer
        layer = self.get_layer_by_name(layer_name)
        if not layer:
            return ExportResult(
                success=False,
                error_message=f"Layer '{layer_name}' not found in project"
            )

        # Determine CRS
        current_projection = projection if projection else layer.sourceCrs()

        # Map datatype to QGIS driver
        driver_name = self.DRIVER_MAP.get(datatype.upper(), datatype)

        logger.debug(f"Exporting layer '{layer.name()}' to {output_path} (driver: {driver_name})")

        try:
            result = QgsVectorFileWriter.writeAsVectorFormat(
                layer,
                os.path.normcase(output_path),
                "UTF-8",
                current_projection,
                driver_name
            )

            if result[0] != QgsVectorFileWriter.NoError:
                error_msg = result[1] if len(result) > 1 else "Unknown error"
                logger.error(f"Export failed for layer '{layer.name()}': {error_msg}")
                return ExportResult(
                    success=False,
                    error_message=error_msg
                )

            # Save style if requested
            if save_styles and style_format:
                from .style_exporter import save_layer_style
                save_layer_style(layer, output_path, style_format, datatype)

            return ExportResult(
                success=True,
                exported_count=1,
                output_path=output_path
            )

        except Exception as e:
            error_msg = str(e)
            logger.error(f"Export exception for layer '{layer.name()}': {error_msg}")
            return ExportResult(
                success=False,
                error_message=error_msg
            )

    def export_to_gpkg(
        self,
        layer_names: List[str],
        output_path: str,
        save_styles: bool
    ) -> ExportResult:
        """
        Export layers to GeoPackage format using QGIS processing.

        Args:
            layer_names: List of layer names to export
            output_path: Output GPKG file path
            save_styles: Whether to include layer styles

        Returns:
            ExportResult with export status
        """
        logger.info(f"Exporting {len(layer_names)} layer(s) to GPKG: {output_path}")

        # Collect layer objects
        layer_objects = []
        for layer_item in layer_names:
            layer_name = layer_item['layer_name'] if isinstance(layer_item, dict) else layer_item
            layer = self.get_layer_by_name(layer_name)
            if layer:
                layer_objects.append(layer)

        if not layer_objects:
            return ExportResult(
                success=False,
                error_message="No valid layers found for GPKG export"
            )

        alg_parameters = {
            'LAYERS': layer_objects,
            'OVERWRITE': True,
            'SAVE_STYLES': save_styles,
            'OUTPUT': output_path
        }

        try:
            output = processing.run("qgis:package", alg_parameters)

            if not output or 'OUTPUT' not in output:
                return ExportResult(
                    success=False,
                    error_message="GPKG export failed: no output returned"
                )

            logger.info(f"GPKG export successful: {output['OUTPUT']}")

            # NOTE: Success message is displayed by finished_handler on main thread
            # DO NOT call iface.messageBar() here - this code runs in background thread!
            # Calling GUI methods from background thread causes crash (access violation)

            return ExportResult(
                success=True,
                exported_count=len(layer_objects),
                output_path=output['OUTPUT']
            )

        except Exception as e:
            logger.error(f"GPKG export failed with exception: {e}")
            return ExportResult(
                success=False,
                error_message=str(e)
            )

    def export_multiple_to_directory(self, config: ExportConfig) -> ExportResult:
        """
        Export multiple layers to a directory (one file per layer).

        Args:
            config: Export configuration

        Returns:
            ExportResult with export statistics
        """
        result = ExportResult(success=True, output_path=config.output_path)

        for layer_name_item in config.layers:
            # Handle both dict and string formats
            layer_name = layer_name_item['layer_name'] if isinstance(layer_name_item, dict) else layer_name_item

            # Build output path for this layer
            ext = get_extension_for_format(config.datatype)
            layer_output = os.path.join(
                config.output_path,
                f"{layer_name}{ext}"
            )

            # Export layer
            layer_result = self.export_single_layer(
                layer_name,
                layer_output,
                config.projection,
                config.datatype,
                config.style_format,
                config.save_styles
            )

            if layer_result.success:
                result.exported_count += 1
            else:
                result.failed_count += 1
                if layer_result.error_message:
                    result.warnings.append(f"{layer_name}: {layer_result.error_message}")

        # Overall success if at least one layer exported
        result.success = result.exported_count > 0
        if result.failed_count > 0:
            result.error_message = f"{result.failed_count} layer(s) failed to export"

        return result

    def export_batch(self, config: ExportConfig) -> ExportResult:
        """
        Export layers in batch mode (directory or zip).

        Args:
            config: Export configuration with batch settings

        Returns:
            ExportResult with export statistics
        """
        import tempfile
        import shutil
        import zipfile

        if config.batch_zip:
            # v5.0: Implement ZIP archive creation
            # Export to temp directory first, then zip
            temp_dir = tempfile.mkdtemp(prefix='filtermate_export_')
            try:
                # Create temp config pointing to temp directory
                temp_config = ExportConfig(
                    layers=config.layers,
                    output_path=temp_dir,
                    datatype=config.datatype,
                    projection=config.projection,
                    style_format=config.style_format,
                    save_styles=config.save_styles,
                    batch_mode=True,
                    batch_zip=False  # Prevent recursion
                )

                # Export to temp directory
                result = self.export_multiple_to_directory(temp_config)

                if result.success:
                    # Create ZIP archive
                    zip_path = config.output_path
                    if not zip_path.endswith('.zip'):
                        zip_path = zip_path + '.zip'

                    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                        for root, dirs, files in os.walk(temp_dir):
                            for file in files:
                                file_path = os.path.join(root, file)
                                arcname = os.path.relpath(file_path, temp_dir)
                                zipf.write(file_path, arcname)

                    result.output_path = zip_path
                    logger.info(f"Created ZIP archive: {zip_path} with {result.exported_count} layers")

                return result

            finally:
                # Clean up temp directory
                if os.path.exists(temp_dir):
                    shutil.rmtree(temp_dir, ignore_errors=True)

        return self.export_multiple_to_directory(config)

    def get_layer_by_name(self, layer_name: str) -> Optional[QgsVectorLayer]:
        """
        Get layer object from project by name.

        Args:
            layer_name: Layer name to search for

        Returns:
            QgsVectorLayer or None if not found
        """
        if not self.project:
            return None

        layers_found = self.project.mapLayersByName(layer_name)
        if layers_found:
            return layers_found[0]

        logger.warning(f"Layer '{layer_name}' not found in project")
        return None
