"""
Create Line From Polygon Action for Right-click Utilities and Shortcuts Hub

Creates a line feature from the outline/boundary of a polygon feature.
Extracts the polygon's exterior ring and creates a new line layer with that boundary.
"""

from .base_action import BaseAction
from ..history_manager import get_history_manager, HistoryManager
from ..i18n import tr
from qgis.core import (
    QgsVectorLayer, QgsFeature, QgsGeometry, QgsField, QgsFields,
    QgsWkbTypes, QgsProject, QgsCoordinateTransform, QgsVectorFileWriter
)
from qgis.PyQt.QtCore import QVariant, QDateTime
from qgis.PyQt.QtWidgets import QMessageBox
import os


class CreateLineFromPolygonAction(BaseAction):
    """Action to create a line feature from polygon boundary."""
    
    def __init__(self):
        """Initialize the action with metadata and configuration."""
        super().__init__()
        
        # Required properties
        self.action_id = "create_line_from_polygon"
        self.name = "Create Line From Polygon"
        self.category = "Geometry"
        self.description = "Create a line feature from the outline/boundary of the selected polygon feature. Extracts the polygon's exterior ring and creates a new line layer. Preserves the original polygon's CRS and optionally includes metadata about the source polygon."
        self.enabled = True
        
        # Action scoping - this works on individual features
        self.set_action_scope('feature')
        self.set_supported_scopes(['feature'])
        
        # Feature type support - only works with polygons
        self.set_supported_click_types(['polygon', 'multipolygon'])
        self.set_supported_geometry_types(['polygon', 'multipolygon'])
    
    def get_settings_schema(self):
        """Define the settings schema for this action."""
        return {
            # OUTPUT SETTINGS - Easy to customize output
            'layer_storage_type': {
                'type': 'choice',
                'default': 'temporary',
                'label': 'Layer Storage Type',
                'description': 'Temporary layers are in-memory only (lost on QGIS close). Permanent layers are saved to disk.',
                'options': ['temporary', 'permanent'],
            },
            'layer_name_template': {
                'type': 'str',
                'default': 'Line_{feature_id}_{layer_name}',
                'label': 'Layer Name Template',
                'description': 'Template for the new line layer name. Available variables: {feature_id}, {layer_name}, {timestamp}',
            },
            'add_to_project': {
                'type': 'bool',
                'default': True,
                'label': 'Add to Project',
                'description': 'Automatically add the created line layer to the project',
            },
            'include_metadata': {
                'type': 'bool',
                'default': True,
                'label': 'Include Metadata',
                'description': 'Include metadata fields in the line layer (source feature ID, source layer name, creation date)',
            },
            
            # BEHAVIOR SETTINGS - User experience options
            'zoom_to_line': {
                'type': 'bool',
                'default': True,
                'label': 'Zoom to Line',
                'description': 'Automatically zoom to the created line feature',
            },
            'show_success_message': {
                'type': 'bool',
                'default': True,
                'label': 'Show Success Message',
                'description': 'Display a success message after creating the line',
            },
        }
    
    def get_setting(self, setting_name, default_value=None):
        """
        Get a setting value for this action.
        
        Args:
            setting_name (str): Name of the setting to retrieve
            default_value: Default value if setting not found
            
        Returns:
            Setting value or default_value
        """
        from qgis.PyQt.QtCore import QSettings
        settings = QSettings()
        key = f"RightClickUtilities/{self.action_id}/{setting_name}"
        return settings.value(key, default_value)
    
    def _extract_boundary_line(self, polygon_geometry):
        """
        Extract the boundary line from a polygon geometry.
        Works with both single and multipart polygons.
        
        Args:
            polygon_geometry (QgsGeometry): Polygon geometry
            
        Returns:
            QgsGeometry: Line geometry representing the boundary, or None if failed
        """
        from qgis.core import QgsPointXY
        
        try:
            # Ensure geometry is valid and not empty
            if polygon_geometry.isEmpty():
                return None
            
            # Try to make valid if needed
            if not polygon_geometry.isGeosValid():
                polygon_geometry = polygon_geometry.makeValid()
                if polygon_geometry.isEmpty():
                    return None
            
            # Method 1: Use boundary() method - this should work for both single and multipart
            try:
                boundary = polygon_geometry.boundary()
                if boundary and not boundary.isEmpty():
                    # For multipart polygons, boundary() returns MultiLineString
                    # For single polygons, it returns LineString
                    # Both are valid line geometries
                    if boundary.type() == QgsWkbTypes.LineGeometry:
                        # This handles both LineString and MultiLineString
                        return boundary
                    
                    # If type check failed but boundary exists, return it anyway
                    # (might be a different line geometry type)
                    return boundary
            except Exception as e:
                print(f"Boundary() method failed: {str(e)}")
            
            # Method 2: Manually extract exterior ring(s) - handles multipart explicitly
            try:
                if polygon_geometry.type() != QgsWkbTypes.PolygonGeometry:
                    return None
                
                # Handle multipart polygons
                if polygon_geometry.isMultipart():
                    # For multipart, extract exterior ring from each part
                    all_lines = []
                    for part in polygon_geometry.asGeometryCollection():
                        if part and part.type() == QgsWkbTypes.PolygonGeometry:
                            # Get exterior ring
                            exterior_ring = part.exteriorRing()
                            if exterior_ring:
                                num_points = exterior_ring.numPoints()
                                if num_points >= 2:
                                    # Extract all points from the ring
                                    points = []
                                    for i in range(num_points):
                                        try:
                                            point = exterior_ring.pointN(i)
                                            if point:
                                                points.append(QgsPointXY(point.x(), point.y()))
                                        except Exception:
                                            continue
                                    
                                    # Create line geometry from points
                                    if len(points) >= 2:
                                        line_geom = QgsGeometry.fromPolylineXY(points)
                                        if line_geom and not line_geom.isEmpty():
                                            all_lines.append(line_geom)
                    
                    # Return first line (or combine all if needed - for now just first)
                    if all_lines:
                        return all_lines[0]
                else:
                    # Single polygon - extract exterior ring
                    exterior_ring = polygon_geometry.exteriorRing()
                    if exterior_ring:
                        num_points = exterior_ring.numPoints()
                        if num_points >= 2:
                            # Extract all points from the ring
                            points = []
                            for i in range(num_points):
                                try:
                                    point = exterior_ring.pointN(i)
                                    if point:
                                        points.append(QgsPointXY(point.x(), point.y()))
                                except Exception:
                                    continue
                            
                            # Create line geometry from points
                            if len(points) >= 2:
                                line_geom = QgsGeometry.fromPolylineXY(points)
                                if line_geom and not line_geom.isEmpty():
                                    return line_geom
            except Exception as e:
                print(f"Manual extraction failed: {str(e)}")
                import traceback
                traceback.print_exc()
            
            # Method 3: Try using asPolygon() / asMultiPolygon() to get polygon structure
            try:
                if polygon_geometry.isMultipart():
                    # Multipart polygon
                    multi_polygon = polygon_geometry.asMultiPolygon()
                    if multi_polygon and len(multi_polygon) > 0:
                        # Get first polygon's exterior ring
                        first_polygon = multi_polygon[0]
                        if first_polygon and len(first_polygon) > 0:
                            exterior_ring_points = first_polygon[0]  # First ring is exterior
                            if len(exterior_ring_points) >= 2:
                                points = [QgsPointXY(p.x(), p.y()) for p in exterior_ring_points]
                                line_geom = QgsGeometry.fromPolylineXY(points)
                                if line_geom and not line_geom.isEmpty():
                                    return line_geom
                else:
                    # Single polygon
                    polygon = polygon_geometry.asPolygon()
                    if polygon and len(polygon) > 0:
                        # First element is exterior ring
                        exterior_ring_points = polygon[0]
                        if len(exterior_ring_points) >= 2:
                            points = [QgsPointXY(p.x(), p.y()) for p in exterior_ring_points]
                            line_geom = QgsGeometry.fromPolylineXY(points)
                            if line_geom and not line_geom.isEmpty():
                                return line_geom
            except Exception as e:
                print(f"asPolygon()/asMultiPolygon() method failed: {str(e)}")
            
            return None
            
        except Exception as e:
            print(f"Error extracting boundary: {str(e)}")
            import traceback
            traceback.print_exc()
            return None
    
    def _create_line_layer(self, line_geometry, layer_name, source_crs, include_metadata, source_feature_id, source_layer_name, output_file_path=None, file_format=None, create_permanent=False):
        """
        Create a new line layer with the boundary geometry.
        
        Args:
            line_geometry (QgsGeometry): Line geometry to add
            layer_name (str): Name for the new layer
            source_crs: CRS of the source layer
            include_metadata (bool): Whether to include metadata fields
            source_feature_id: ID of the source polygon feature
            source_layer_name: Name of the source layer
            output_file_path (str): Path where to save the file (only for permanent layers)
            file_format (str): File format (ESRI Shapefile, GPKG, etc.) (only for permanent layers)
            create_permanent (bool): If True, create file-based layer; if False, create temporary memory layer
            
        Returns:
            QgsVectorLayer: New layer containing the line, or None if failed
        """
        try:
            # Create temporary memory layer for line
            crs_string = source_crs.authid() if source_crs.authid() else source_crs.toWkt()
            temp_layer = QgsVectorLayer(f"LineString?crs={crs_string}", layer_name, "memory")
            
            if not temp_layer.isValid():
                return None
            
            # Add fields
            fields = QgsFields()
            if include_metadata:
                fields.append(QgsField("source_feature_id", QVariant.Int))
                fields.append(QgsField("source_layer", QVariant.String))
                fields.append(QgsField("created_at", QVariant.String))
            
            temp_layer.dataProvider().addAttributes(fields.toList())
            temp_layer.updateFields()
            
            # Create feature
            feature = QgsFeature()
            feature.setGeometry(line_geometry)
            
            # Set attributes
            if include_metadata:
                feature.setAttributes([
                    source_feature_id,
                    source_layer_name,
                    QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
                ])
            
            # Add feature to layer
            temp_layer.dataProvider().addFeatures([feature])
            temp_layer.updateExtents()
            
            # If creating temporary layer, return it directly
            if not create_permanent:
                # determine created fid
                created_fid = None
                try:
                    for f in temp_layer.getFeatures():
                        created_fid = f.id()
                        break
                except Exception:
                    created_fid = None
                return temp_layer, created_fid
            
            # Otherwise, write to file and load it
            if not output_file_path or not file_format:
                return None
            
            # Write to file
            options = QgsVectorFileWriter.SaveVectorOptions()
            options.driverName = file_format
            options.fileEncoding = "UTF-8"
            
            if file_format == "GPKG":
                options.layerName = layer_name
            
            # For shapefiles, use base path without extension
            if file_format == "ESRI Shapefile":
                write_path = output_file_path  # Already without extension
            else:
                write_path = output_file_path  # With extension
            
            error_code, error_message = QgsVectorFileWriter.writeAsVectorFormat(
                temp_layer,
                write_path,
                options
            )
            
            if error_code != QgsVectorFileWriter.NoError:
                self.show_error(tr("Error"), tr("Failed to write line layer file: {msg}").format(msg=error_message))
                return None
            
            # For shapefiles, construct the full path to load
            if file_format == "ESRI Shapefile":
                load_path = output_file_path + ".shp"
            else:
                load_path = output_file_path
            
            # Load the file-based layer
            file_layer = QgsVectorLayer(load_path, layer_name, "ogr")
            if not file_layer.isValid():
                self.show_error(tr("Error"), tr("Failed to load created line layer file"))
                return None, None

            # determine created fid
            created_fid = None
            try:
                for f in file_layer.getFeatures():
                    created_fid = f.id()
                    break
            except Exception:
                created_fid = None

            return file_layer, created_fid
            
        except Exception as e:
            print(f"Error creating line layer: {str(e)}")
            return None
    
    def execute(self, context):
        """Execute the create line from polygon action."""
        # Get settings with proper type conversion
        try:
            schema = self.get_settings_schema()
            layer_storage_type = str(self.get_setting('layer_storage_type', schema['layer_storage_type']['default']))
            layer_name_template = str(self.get_setting('layer_name_template', schema['layer_name_template']['default']))
            add_to_project = bool(self.get_setting('add_to_project', schema['add_to_project']['default']))
            include_metadata = bool(self.get_setting('include_metadata', schema['include_metadata']['default']))
            zoom_to_line = bool(self.get_setting('zoom_to_line', schema['zoom_to_line']['default']))
            show_success_message = bool(self.get_setting('show_success_message', schema['show_success_message']['default']))
        except (ValueError, TypeError) as e:
            self.show_error(tr("Error"), tr("Invalid setting values: {error}").format(error=str(e)))
            return
        
        # Extract context elements
        detected_features = context.get('detected_features', [])
        canvas = context.get('canvas')
        
        if not detected_features:
            self.show_error(tr("Error"), tr("No polygon features found at this location"))
            return
        
        # Get the first (closest) detected feature
        detected_feature = detected_features[0]
        feature = detected_feature.feature
        layer = detected_feature.layer
        
        try:
            # Get feature geometry
            polygon_geometry = feature.geometry()
            if not polygon_geometry:
                self.show_error(tr("Error"), tr("Feature has no geometry"))
                return
            
            if polygon_geometry.isEmpty():
                self.show_error(tr("Error"), tr("Feature has empty geometry"))
                return
            
            # Validate that this is a polygon feature
            if polygon_geometry.type() != QgsWkbTypes.PolygonGeometry:
                self.show_error(tr("Error"), tr("This action only works with polygon features"))
                return
            
            # Extract boundary line from polygon
            line_geometry = self._extract_boundary_line(polygon_geometry)
            
            if not line_geometry:
                # Provide more detailed error information
                geom_type = polygon_geometry.type()
                is_multipart = polygon_geometry.isMultipart()
                is_valid = polygon_geometry.isGeosValid()
                is_empty = polygon_geometry.isEmpty()
                
                error_msg = f"Failed to extract boundary from polygon.\n\n"
                error_msg += f"Geometry Type: {geom_type}\n"
                error_msg += f"Is Multipart: {is_multipart}\n"
                error_msg += f"Is Valid: {is_valid}\n"
                error_msg += f"Is Empty: {is_empty}\n\n"
                error_msg += "Please check the polygon geometry."
                
                self.show_error(tr("Error"), error_msg)
                return
            
            if line_geometry.isEmpty():
                self.show_error(tr("Error"), tr("Extracted boundary is empty. The polygon may be invalid."))
                return
            
            # Ensure the geometry is valid
            if not line_geometry.isGeosValid():
                # Try to fix the geometry
                line_geometry = line_geometry.makeValid()
                if line_geometry.isEmpty():
                    self.show_error(tr("Error"), tr("Could not create valid line geometry from polygon boundary."))
                    return
            
            # Generate layer name
            timestamp = QDateTime.currentDateTime().toString("yyyyMMdd_hhmmss")
            layer_name = layer_name_template.format(
                feature_id=feature.id(),
                layer_name=layer.name().replace(' ', '_').replace('/', '_').replace('\\', '_'),
                timestamp=timestamp
            )
            
            # Ensure unique layer name in project
            project = QgsProject.instance()
            base_layer_name = layer_name
            counter = 1
            while project.mapLayersByName(layer_name):
                layer_name = f"{base_layer_name}_{counter}"
                counter += 1
            
            # Check layer storage type setting
            create_permanent = (layer_storage_type == 'permanent')
            
            # Prepare variables for permanent layer creation
            output_file_path = None
            file_format = None
            
            if create_permanent:
                # Prompt user for save location
                from qgis.PyQt.QtWidgets import QFileDialog
                save_path, _ = QFileDialog.getSaveFileName(
                    None, tr("Save Line Layer As"), "", "GeoPackage (*.gpkg);;Shapefile (*.shp)"
                )
                if not save_path:
                    return  # User cancelled
                
                output_file_path = save_path
                file_format = "GPKG" if save_path.endswith('.gpkg') else "ESRI Shapefile"
            
            # Create the line layer
            line_layer, created_fid = self._create_line_layer(
                line_geometry,
                layer_name,
                layer.crs(),
                include_metadata,
                feature.id(),
                layer.name(),
                output_file_path,
                file_format,
                create_permanent
            )

            if not line_layer:
                self.show_error(tr("Error"), tr("Failed to create line layer"))
                return
            
            # Add to project if requested
            if add_to_project:
                project.addMapLayer(line_layer)

            # Record history entry (CREATE_LAYER) so this action can be undone/redone
            try:
                # get created feature if possible
                created_feature = None
                if created_fid is not None:
                    from qgis.core import QgsFeatureRequest
                    for f in line_layer.getFeatures(QgsFeatureRequest().setFilterFid(created_fid)):
                        created_feature = f
                        break

                feature_backup = self.create_feature_backup(created_feature, line_layer) if created_feature else ({'fid': created_fid} if created_fid is not None else None)

                layer_def = {
                    'layer_name': line_layer.name(),
                    'crs': line_layer.crs().authid() if line_layer.crs().isValid() else '',
                    'geometry_type': QgsWkbTypes.displayString(line_layer.wkbType()),
                    'fields': [],
                    'features': [feature_backup] if feature_backup else []
                }
                for fld in line_layer.fields():
                    try:
                        layer_def['fields'].append({'name': fld.name(), 'qmeta_type': fld.type()})
                    except Exception:
                        layer_def['fields'].append({'name': fld.name()})

                hm = get_history_manager()
                hm.record(
                    action_id=self.action_id,
                    action_name=self.name,
                    description=f"Created line from polygon {feature.id()} in layer '{line_layer.name()}'",
                    undo_type=HistoryManager.UNDO_TYPE_CREATE_LAYER,
                    can_undo=True,
                    undo_payload={'layer_definition': layer_def},
                    layers=[self.create_layer_descriptor(line_layer)],
                    features=[feature_backup] if feature_backup else [],
                    atomic=True,
                    meta={'layer_was_created': True, 'source_feature_id': feature.id(), 'source_layer_id': layer.id()}
                )
            except Exception:
                pass
            
            # Zoom to line if requested
            if zoom_to_line and canvas:
                # Get line extent
                line_extent = line_geometry.boundingBox()
                
                # CRS handling - transform if needed
                canvas_crs = canvas.mapSettings().destinationCrs()
                layer_crs = layer.crs()
                
                if canvas_crs != layer_crs:
                    transform = QgsCoordinateTransform(layer_crs, canvas_crs, QgsProject.instance())
                    try:
                        line_extent = transform.transformBoundingBox(line_extent)
                    except Exception as e:
                        if show_success_message:
                            self.show_warning(tr("Warning"), tr("Could not zoom to line due to CRS transformation: {error}").format(error=str(e)))
                
                # Add buffer to extent for better visualization
                buffer_percentage = 10.0  # 10% buffer
                width = line_extent.width()
                height = line_extent.height()
                buffer_x = width * (buffer_percentage / 100.0)
                buffer_y = height * (buffer_percentage / 100.0)
                
                line_extent.grow(max(buffer_x, buffer_y))
                
                # Zoom to extent
                canvas.setExtent(line_extent)
                canvas.refresh()
            
            # Show success message
            if show_success_message:
                # Calculate line length
                line_length = line_geometry.length()
                
                # Get unit information
                crs = layer.crs()
                unit_name = "units"
                if crs.isGeographic():
                    unit_name = "degrees"
                elif crs.isValid() and crs.mapUnits() != 0:
                    unit_name = crs.mapUnits().name().lower()
                
                message = f"Line created successfully from polygon boundary!\n\n"
                message += f"Source Feature ID: {feature.id()}\n"
                message += f"Source Layer: {layer.name()}\n"
                message += f"Line Layer: {layer_name}\n"
                message += f"Line Length: {line_length:.2f} {unit_name}\n"
                
                if create_permanent:
                    message += f"\nLayer type: Permanent (saved to disk)"
                else:
                    message += f"\nLayer type: Temporary (memory only)"
                
                if add_to_project:
                    message += "\n\nLine layer has been added to the project."
                
                # Translate the success message template and format with values
                crs = layer.crs()
                unit_name = "units"
                if crs.isGeographic():
                    unit_name = tr("degrees")
                elif crs.isValid() and crs.mapUnits() != 0:
                    try:
                        unit_name = crs.mapUnits().name().lower()
                    except Exception:
                        unit_name = tr("units")

                info_message = (
                    tr("Line created successfully from polygon boundary!\n\nSource Feature ID: {feature_id}\nSource Layer: {source_layer}\nLine Layer: {line_layer}\nLine Length: {line_length:.2f} {unit_name}\n{layer_type}{project_note}")
                    .format(
                        feature_id=feature.id(),
                        source_layer=layer.name(),
                        line_layer=layer_name,
                        line_length=line_length,
                        unit_name=unit_name,
                        layer_type=(tr("Layer type: Permanent (saved to disk)") if create_permanent else tr("Layer type: Temporary (memory only)")),
                        project_note=(tr("\n\nLine layer has been added to the project.") if add_to_project else "")
                    )
                )
                self.show_info(tr("Line Created"), info_message)
            
        except Exception as e:
            self.show_error(tr("Error"), tr("Failed to create line from polygon: {error}").format(error=str(e)))


# REQUIRED: Create global instance for automatic discovery
create_line_from_polygon_action = CreateLineFromPolygonAction()

