from PyQt5 import uic
from PyQt5.QtWidgets import QDialog, QTableWidgetItem, QComboBox, QTextEdit, QPushButton
from PyQt5.QtCore import Qt, QVariant
from qgis.core import QgsProject, QgsVectorLayer, QgsField, QgsWkbTypes, QgsVectorDataProvider
import datetime

class DataLoaderDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        print("DataLoaderDialog: __init__ called. Loading UI...") # Debug print
        uic.loadUi(self.get_ui_path(), self)
        self.populate_layers()
        self.combo_target_layer.currentIndexChanged.connect(self.on_target_layer_changed)
        self.combo_source_layer.currentIndexChanged.connect(self.update_field_mapping_table)
        self.textEdit_log = self.findChild(QTextEdit, 'textEdit_log') # Get the log text edit
        self.textEdit_about = self.findChild(QTextEdit, 'textEdit_about') # Get the about text edit
        self.add_tips_and_hints()
        self.setup_about_tab()

        # Connect clear log button
        self.clear_log_button = self.findChild(QPushButton, 'clear_log_button')
        if self.clear_log_button:
            self.clear_log_button.clicked.connect(self.clear_log)

        # Add button to show guide panel if not visible
        self.show_guide_button = QPushButton("📖 Show Usage Guide")
        self.show_guide_button.setToolTip("Open the Data Loader Guide panel with tips and instructions")
        self.show_guide_button.clicked.connect(self.show_guide_panel)
        
        # Add the button to the dialog layout (try to find a suitable place)
        try:
            # Try to add to the main layout
            main_layout = self.layout()
            if main_layout:
                main_layout.addWidget(self.show_guide_button)
        except:
            # If that fails, we'll handle it in the parent plugin
            pass

        # Set comprehensive tooltips
        self.setup_tooltips()
        
        # Set status tips for better user experience
        self.setup_status_tips()

    def setup_tooltips(self):
        """Set up comprehensive tooltips for all UI elements"""
        # Layer selection tooltips
        self.combo_target_layer.setToolTip(
            "Target Layer: Select the destination layer where data will be loaded.\n"
            "• This layer will receive new features from the source layer\n"
            "• Must be a vector layer with compatible geometry type\n"
            "• Layer will be modified (features added)"
        )
        
        self.combo_source_layer.setToolTip(
            "Source Layer: Select the layer from which data will be copied.\n"
            "• Features from this layer will be copied to the target layer\n"
            "• Must be a vector layer with compatible geometry type\n"
            "• This layer remains unchanged during the process"
        )
        
        # Field mapping table tooltip
        self.table_field_mapping.setToolTip(
            "Field Mapping: Map fields from source to target layer.\n"
            "• Left column shows target layer fields\n"
            "• Right column allows selection of corresponding source fields\n"
            "• Only compatible field types are shown in dropdowns\n"
            "• Fields can be left unmapped (will use default values)\n"
            "• Automatic matching attempts to find fields with same names"
        )
        
        # Tab tooltips
        tab_widget = self.findChild(self.tabWidget.__class__, 'tabWidget')
        if tab_widget:
            tab_widget.setTabToolTip(0, "Parameters: Configure layer selection and field mapping")
            tab_widget.setTabToolTip(1, "Log: View process messages, errors, and progress information")
            tab_widget.setTabToolTip(2, "About: Plugin information, features, and quick start guide")
        
        # Log area tooltip
        if self.textEdit_log:
            self.textEdit_log.setToolTip(
                "Process Log: Shows detailed information about the data loading process.\n"
                "• Progress updates during data transfer\n"
                "• Error messages and warnings\n"
                "• Success confirmations\n"
                "• Tips and usage instructions"
            )
        
        # Clear log button tooltip
        if self.clear_log_button:
            self.clear_log_button.setToolTip("Clear all messages from the log area")

    def setup_status_tips(self):
        """Set up status bar tips for UI elements"""
        self.combo_target_layer.setStatusTip("Choose the layer that will receive the copied data")
        self.combo_source_layer.setStatusTip("Choose the layer to copy data from")
        self.table_field_mapping.setStatusTip("Configure how fields are mapped between layers")
        if self.clear_log_button:
            self.clear_log_button.setStatusTip("Clear the log messages")

    def get_ui_path(self):
        import os
        return os.path.join(os.path.dirname(__file__), 'main_plugin_dialog_base.ui')

    def populate_layers(self):
        """Populate layer combo boxes with vector layers"""
        layers = QgsProject.instance().mapLayers().values()
        vector_layers = [layer for layer in layers if isinstance(layer, QgsVectorLayer)]
        
        # Populate target layer combo (all vector layers)
        self.combo_target_layer.clear()
        self.combo_target_layer.addItem("Select target layer...", None)
        for layer in vector_layers:
            geom_type_name = self.get_geometry_type_name(layer.geometryType())
            layer_info = f"{layer.name()} ({geom_type_name})"
            self.combo_target_layer.addItem(layer_info, layer.id())
        
        # Populate source layer combo (will be filtered based on target selection)
        self.populate_source_layers()

    def populate_source_layers(self):
        """Populate source layer combo based on target layer selection"""
        self.combo_source_layer.clear()
        self.combo_source_layer.addItem("Select source layer...", None)
        
        target_layer_id = self.combo_target_layer.currentData()
        if not target_layer_id:
            return
            
        target_layer = QgsProject.instance().mapLayer(target_layer_id)
        if not target_layer:
            return
            
        target_geom_type = target_layer.geometryType()
        target_crs = target_layer.crs()
        
        layers = QgsProject.instance().mapLayers().values()
        vector_layers = [layer for layer in layers if isinstance(layer, QgsVectorLayer)]
        
        compatible_count = 0
        different_crs_count = 0
        
        for layer in vector_layers:
            if layer.id() == target_layer_id:
                continue  # Skip the target layer itself
                
            source_geom_type = layer.geometryType()
            source_crs = layer.crs()
            
            # Check geometry compatibility
            if self.are_geometries_compatible(target_geom_type, source_geom_type):
                geom_type_name = self.get_geometry_type_name(source_geom_type)
                
                # Check CRS compatibility
                if source_crs != target_crs:
                    different_crs_count += 1
                    layer_info = f"{layer.name()} ({geom_type_name}) ⚠️ Different CRS"
                    self.combo_source_layer.addItem(layer_info, layer.id())
                else:
                    layer_info = f"{layer.name()} ({geom_type_name})"
                    self.combo_source_layer.addItem(layer_info, layer.id())
                
                compatible_count += 1
        
        # Add status message
        if compatible_count == 0:
            target_geom_name = self.get_geometry_type_name(target_geom_type)
            self.combo_source_layer.addItem(f"❌ No compatible {target_geom_name} layers found", None)
            self.log_warning(f"No source layers with compatible geometry type found for target layer '{target_layer.name()}'")
        elif different_crs_count > 0:
            self.log_warning(f"Found {different_crs_count} compatible layers with different coordinate systems. Data will be reprojected automatically.")

    def get_geometry_type_name(self, geom_type):
        """Get human-readable geometry type name"""
        type_names = {
            QgsWkbTypes.PointGeometry: "Point",
            QgsWkbTypes.LineGeometry: "Line", 
            QgsWkbTypes.PolygonGeometry: "Polygon",
            QgsWkbTypes.UnknownGeometry: "Unknown",
            QgsWkbTypes.NullGeometry: "No Geometry"
        }
        return type_names.get(geom_type, "Unknown")

    def are_geometries_compatible(self, target_type, source_type):
        """Check if source and target geometry types are compatible"""
        # Exact match is always compatible
        if target_type == source_type:
            return True
            
        # Define compatibility rules
        compatibility_rules = {
            QgsWkbTypes.PointGeometry: [QgsWkbTypes.PointGeometry],
            QgsWkbTypes.LineGeometry: [QgsWkbTypes.LineGeometry],
            QgsWkbTypes.PolygonGeometry: [QgsWkbTypes.PolygonGeometry]
        }
        
        return source_type in compatibility_rules.get(target_type, [])

    def on_target_layer_changed(self):
        """Handle target layer selection change"""
        self.populate_source_layers()
        self.update_field_mapping_table()

    def update_field_mapping_table(self):
        self.table_field_mapping.clearContents()
        self.table_field_mapping.setRowCount(0)

        target_layer_id = self.combo_target_layer.currentData()
        source_layer_id = self.combo_source_layer.currentData()

        if not target_layer_id or not source_layer_id:
            return

        target_layer = QgsProject.instance().mapLayer(target_layer_id)
        source_layer = QgsProject.instance().mapLayer(source_layer_id)

        if not target_layer or not source_layer:
            return

        target_fields = target_layer.fields()
        source_fields = source_layer.fields()

        self.table_field_mapping.setRowCount(len(target_fields))

        for i, target_field in enumerate(target_fields):
            target_field_item = QTableWidgetItem(target_field.name())
            target_field_item.setFlags(target_field_item.flags() & ~Qt.ItemIsEditable)
            self.table_field_mapping.setItem(i, 0, target_field_item)

            source_field_combo = QComboBox()
            source_field_combo.addItem("<None>", None)
            
            # Only list source fields that are compatible with the target field
            for source_field in source_fields:
                if self.is_field_compatible(target_field, source_field):
                    source_field_combo.addItem(source_field.name(), source_field.name())
            
            self.table_field_mapping.setCellWidget(i, 1, source_field_combo)

            # Attempt to pre-select matching fields
            if target_field.name() in [f.name() for f in source_fields]:
                idx = source_field_combo.findText(target_field.name())
                if idx != -1:
                    source_field_combo.setCurrentIndex(idx)

        self.table_field_mapping.resizeColumnsToContents()

    def add_tips_and_hints(self):
        """Initialize log with basic processing information"""
        initial_info = """
        <div style="font-family: Arial, sans-serif; line-height: 1.4; padding: 10px;">
        <h3 style="color: #2E7D32; margin-top: 0;">📋 Processing Log</h3>
        <p style="color: #666; margin-bottom: 5px;">Ready to process data transfer.</p>
        <p style="color: #666; font-size: 12px;"><em>Configure parameters and click OK to start. Progress will be shown here.</em></p>
        </div>
        """
        self.textEdit_log.setHtml(initial_info)

    def is_field_compatible(self, target_field, source_field):
        """Check if source field is compatible with target field"""
        # Basic compatibility check: allow same type or conversion between numeric types
        if target_field.type() == source_field.type():
            return True
        
        # Allow conversion between different numeric types
        if target_field.isNumeric() and source_field.isNumeric():
            return True
        
        # Allow any type to string
        # Use QVariant.String instead of QgsField.Type.String for compatibility
        if target_field.type() == QVariant.String:
            return True
            
        return False

    def setup_about_tab(self):
        """Set up the About tab content"""
        about_content = """
        <div style="font-family: Arial, sans-serif; line-height: 1.4; padding: 10px;">
        <h2 style="color: #2E7D32; text-align: center; margin-bottom: 20px;">🔄 Data Loader Plugin</h2>
        
        <div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 15px;">
        <h3 style="color: #1976D2; margin-top: 0;">📋 Overview</h3>
        <p>Professional data transfer tool for QGIS with smart field mapping, comprehensive validation, and detailed logging. Transfer features between any vector layers with confidence.</p>
        </div>
        
        <h3 style="color: #1976D2;">✨ Key Features</h3>
        <ul style="margin-left: 20px;">
            <li><strong>Smart Field Mapping:</strong> Automatic type compatibility checking</li>
            <li><strong>Full PostGIS Support:</strong> Enterprise database integration</li>
            <li><strong>Input Validation:</strong> Prevents errors before processing</li>
            <li><strong>Progress Tracking:</strong> Real-time updates and logging</li>
            <li><strong>Transaction Safety:</strong> Rollback on errors</li>
            <li><strong>Cross-Platform:</strong> Works on Windows, macOS, Linux</li>
        </ul>
        
        <h3 style="color: #1976D2;">📊 Supported Data Sources</h3>
        <ul style="margin-left: 20px;">
            <li><strong>File Formats:</strong> Shapefile, GeoPackage, GeoJSON, KML, CSV</li>
            <li><strong>Databases:</strong> PostGIS, SpatiaLite, Oracle, SQL Server</li>
            <li><strong>Web Services:</strong> WFS, ArcGIS Feature Services</li>
            <li><strong>Memory Layers:</strong> Temporary and virtual layers</li>
        </ul>
        
        <h3 style="color: #1976D2;">🚀 Quick Start</h3>
        <ol style="margin-left: 20px;">
            <li>Select your <strong>Target Layer</strong> (destination)</li>
            <li>Select your <strong>Source Layer</strong> (origin)</li>
            <li>Configure <strong>Field Mappings</strong> in the table</li>
            <li>Click <strong>OK</strong> to start the transfer</li>
            <li>Monitor progress in the <strong>Log</strong> tab</li>
        </ol>
        
        <div style="background-color: #E8F5E8; padding: 10px; border-left: 4px solid #4CAF50; margin: 15px 0;">
        <strong>💡 Tip:</strong> Use the dockable guide panel (View → Panels → Data Loader Guide) for detailed instructions and troubleshooting.
        </div>
        
        <hr style="margin: 20px 0; border: 1px solid #ddd;">
        
        <div style="text-align: center; color: #666; font-size: 12px;">
        <p><strong>Data Loader Plugin v1.0.0</strong><br>
        Professional data transfer for QGIS<br>
        <em>Compatible with QGIS 3.0+</em></p>
        </div>
        </div>
        """
        
        if self.textEdit_about:
            self.textEdit_about.setHtml(about_content)

    def validate_inputs(self):
        """Validate user inputs before processing"""
        errors = []
        warnings = []
        
        # Check target layer selection
        target_layer_id = self.combo_target_layer.currentData()
        if not target_layer_id:
            errors.append("Please select a target layer")
            return False, errors, warnings
            
        # Check source layer selection
        source_layer_id = self.combo_source_layer.currentData()
        if not source_layer_id:
            errors.append("Please select a source layer")
            return False, errors, warnings
        
        # Get layers
        target_layer = QgsProject.instance().mapLayer(target_layer_id)
        source_layer = QgsProject.instance().mapLayer(source_layer_id)
        
        if not target_layer or not source_layer:
            errors.append("Selected layers are not valid")
            return False, errors, warnings
        
        # Validate geometry types
        target_geom_type = target_layer.geometryType()
        source_geom_type = source_layer.geometryType()
        
        if not self.are_geometries_compatible(target_geom_type, source_geom_type):
            target_type_name = self.get_geometry_type_name(target_geom_type)
            source_type_name = self.get_geometry_type_name(source_geom_type)
            errors.append(f"Incompatible geometry types: Cannot transfer {source_type_name} features to {target_type_name} layer")
            return False, errors, warnings
        
        # Validate coordinate reference systems
        target_crs = target_layer.crs()
        source_crs = source_layer.crs()
        
        if target_crs != source_crs:
            warnings.append(f"Different coordinate systems detected:")
            warnings.append(f"  • Target: {target_crs.authid()} - {target_crs.description()}")
            warnings.append(f"  • Source: {source_crs.authid()} - {source_crs.description()}")
            warnings.append("Data will be automatically reprojected during transfer.")
        
        # Check if target layer is editable
        if not target_layer.isEditable() and not target_layer.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures:
            warnings.append(f"Target layer '{target_layer.name()}' may not be editable. Ensure you have write permissions.")
        
        # Check feature counts for performance warnings
        source_feature_count = source_layer.featureCount()
        if source_feature_count > 10000:
            warnings.append(f"Large dataset detected: {source_feature_count:,} features in source layer.")
            warnings.append("Processing may take some time. Consider processing in smaller batches for very large datasets.")
        
        # Check for field mapping
        field_mappings = self.get_field_mapping_summary()
        
        if len(field_mappings) == 0:
            warnings.append("No fields are mapped. Features will be transferred with only geometry and default field values.")
        
        return True, errors, warnings

    def show_validation_dialog(self, errors, warnings):
        """Show validation results to user"""
        from PyQt5.QtWidgets import QMessageBox
        
        if errors:
            # Show errors - these prevent processing
            error_text = "❌ **Cannot proceed due to the following errors:**\n\n"
            for i, error in enumerate(errors, 1):
                error_text += f"{i}. {error}\n"
            error_text += "\nPlease fix these issues before continuing."
            
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Data Loader - Validation Errors")
            msg.setText(error_text)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()
            return False
        
        if warnings:
            # Show warnings - user can choose to continue
            warning_text = "⚠️ **Please review the following warnings:**\n\n"
            for i, warning in enumerate(warnings, 1):
                warning_text += f"{i}. {warning}\n"
            warning_text += "\nDo you want to continue with the data transfer?"
            
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Data Loader - Validation Warnings")
            msg.setText(warning_text)
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg.setDefaultButton(QMessageBox.Yes)
            
            result = msg.exec_()
            return result == QMessageBox.Yes
        
        return True

    def preview_operation(self):
        """Generate and log operation preview"""
        target_layer_id = self.combo_target_layer.currentData()
        source_layer_id = self.combo_source_layer.currentData()
        
        if not target_layer_id or not source_layer_id:
            return
            
        target_layer = QgsProject.instance().mapLayer(target_layer_id)
        source_layer = QgsProject.instance().mapLayer(source_layer_id)
        
        if not target_layer or not source_layer:
            return
        
        # Log operation details
        self.log_info("=== Data Transfer Operation Preview ===")
        self.log_info(f"Source Layer: {source_layer.name()} ({self.get_geometry_type_name(source_layer.geometryType())})")
        self.log_info(f"Target Layer: {target_layer.name()} ({self.get_geometry_type_name(target_layer.geometryType())})")
        self.log_info(f"Features to transfer: {source_layer.featureCount():,}")
        
        # CRS information
        if source_layer.crs() != target_layer.crs():
            self.log_warning(f"Coordinate system transformation required:")
            self.log_warning(f"  From: {source_layer.crs().authid()} - {source_layer.crs().description()}")
            self.log_warning(f"  To: {target_layer.crs().authid()} - {target_layer.crs().description()}")
        else:
            self.log_info(f"Coordinate System: {target_layer.crs().authid()}")
        
        # Field mapping summary
        field_mappings = self.get_field_mapping_summary()
        
        if field_mappings:
            self.log_info(f"Field mappings ({len(field_mappings)} fields):")
            for target_field, source_field in field_mappings.items():
                self.log_info(f"  {target_field} ← {source_field}")
        else:
            self.log_warning("No field mappings configured - only geometry will be transferred")
        
        self.log_info("Ready to start data transfer...")

    def get_field_mapping_summary(self):
        """Get a summary of current field mappings"""
        mappings = {}
        for i in range(self.table_field_mapping.rowCount()):
            target_field_name = self.table_field_mapping.item(i, 0).text()
            source_field_combo = self.table_field_mapping.cellWidget(i, 1)
            source_field_name = source_field_combo.currentData()
            if source_field_name:
                mappings[target_field_name] = source_field_name
        return mappings

    def append_log(self, message, level="INFO"):
        """Enhanced logging with timestamps and severity levels"""
        timestamp = datetime.datetime.now().strftime("%H:%M:%S")
        
        # Format message with timestamp and level
        if level == "ERROR":
            formatted_message = f"<span style='color: red;'>[{timestamp}] ERROR: {message}</span>"
        elif level == "WARNING":
            formatted_message = f"<span style='color: orange;'>[{timestamp}] WARNING: {message}</span>"
        elif level == "SUCCESS":
            formatted_message = f"<span style='color: green;'>[{timestamp}] SUCCESS: {message}</span>"
        elif level == "PROGRESS":
            formatted_message = f"<span style='color: blue;'>[{timestamp}] PROGRESS: {message}</span>"
        else:  # INFO
            formatted_message = f"[{timestamp}] INFO: {message}"
        
        # Append to log with HTML formatting
        if self.textEdit_log:
            self.textEdit_log.append(formatted_message)
            # Auto-scroll to bottom
            scrollbar = self.textEdit_log.verticalScrollBar()
            scrollbar.setValue(scrollbar.maximum())

    def log_info(self, message):
        """Log an informational message"""
        self.append_log(message, "INFO")

    def log_error(self, message):
        """Log an error message"""
        self.append_log(message, "ERROR")

    def log_warning(self, message):
        """Log a warning message"""
        self.append_log(message, "WARNING")

    def log_success(self, message):
        """Log a success message"""
        self.append_log(message, "SUCCESS")

    def log_progress(self, message):
        """Log a progress message"""
        self.append_log(message, "PROGRESS")

    def clear_log(self):
        """Clear the log and add initial processing info"""
        if self.textEdit_log:
            self.textEdit_log.clear()
            self.add_tips_and_hints()

    def show_guide_panel(self):
        """Show the guide panel - this will be connected to the main plugin"""
        # This method will be overridden by the main plugin to show the dock widget
        from PyQt5.QtWidgets import QMessageBox
        QMessageBox.information(self, "Guide Panel", 
                              "The guide panel should be visible in the QGIS interface. "
                              "Look for 'Data Loader Guide' in the View > Panels menu or "
                              "in the right side of the QGIS window.")

    def export_log(self):
        """Export log contents to a file"""
        try:
            from PyQt5.QtWidgets import QFileDialog
            filename, _ = QFileDialog.getSaveFileName(
                self, 
                "Export Log", 
                f"data_loader_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
                "Text Files (*.txt);;All Files (*)"
            )
            if filename:
                with open(filename, 'w', encoding='utf-8') as f:
                    # Remove HTML tags for plain text export
                    import re
                    plain_text = re.sub('<[^<]+?>', '', self.textEdit_log.toPlainText())
                    f.write(plain_text)
                self.log_success(f"Log exported to: {filename}")
        except Exception as e:
            self.log_error(f"Failed to export log: {str(e)}")


