import os

from qgis.PyQt.QtWidgets import (
    QDialog,
    QVBoxLayout,
    QPushButton,
    QLabel,
    QFileDialog,
    QMessageBox,
)
from qgis.PyQt.QtCore import QTimer, Qt
from qgis.PyQt.QtGui import QPixmap

from qgis.core import QgsProject, QgsEditFormConfig

from .zip_loader import load_cadastre_zip, build_package_from_session_files
from .session_builder import build_session_layers
from .session_manager import (
    create_session_from_layers,
    open_existing_session,
    save_session_layers,
)
from .validator import validate_project, format_validation_report
from .exporter import export_final_zip
from .managed_fields import ManagedFieldsController, update_area_for_all_features
from .cadastre_editing import (
    initial_values_for_new_parcel,
    initial_values_for_new_building,
    apply_attributes,
    enforce_no_overlap_same_layer,
    sync_buildings_parcno,
)
from .external_import import (
    import_parcels_from_file,
    import_buildings_from_file,
)


class MainDialog(QDialog):
    def __init__(self, plugin_dir=None):
        super().__init__()

        self.plugin_dir = plugin_dir or os.path.dirname(__file__)

        self.setWindowTitle("Cadastre ZIP Editor")
        self.resize(700, 620)

        self.package = None
        self.session_info = None
        self.validation_result = None
        self.managed_fields = ManagedFieldsController()

        self._suppress_parcel_events = False
        self._suppress_building_events = False
        self._parcel_connected = set()
        self._building_connected = set()

        layout = QVBoxLayout()

        # -------------------------------------------------
        # LOGO: përdor logo.png, jo icon.png
        # -------------------------------------------------
        logo_label = QLabel()
        logo_label.setAlignment(Qt.AlignCenter)

        logo_path = os.path.join(self.plugin_dir, "logo.png")
        icon_path = os.path.join(self.plugin_dir, "icon.png")

        pix = QPixmap()
        if os.path.exists(logo_path):
            pix = QPixmap(logo_path)
        elif os.path.exists(icon_path):
            pix = QPixmap(icon_path)

        if not pix.isNull():
            logo_label.setPixmap(pix.scaledToWidth(180, Qt.SmoothTransformation))
            layout.addWidget(logo_label)

        self.info_label = QLabel("Load cadastre ZIP or open an existing session.")
        self.info_label.setWordWrap(True)
        layout.addWidget(self.info_label)

        self.load_zip_btn = QPushButton("Load Source ZIP")
        layout.addWidget(self.load_zip_btn)

        self.open_session_btn = QPushButton("Open Existing Session")
        layout.addWidget(self.open_session_btn)

        self.save_session_btn = QPushButton("Save Session")
        self.save_session_btn.setEnabled(False)
        layout.addWidget(self.save_session_btn)

        self.open_layers_btn = QPushButton("Open Editable Layers From ZIP")
        self.open_layers_btn.setEnabled(False)
        layout.addWidget(self.open_layers_btn)

        self.create_session_btn = QPushButton("Create Persistent Session")
        self.create_session_btn.setEnabled(False)
        layout.addWidget(self.create_session_btn)

        self.import_parcels_btn = QPushButton("Import Parcels From File")
        self.import_parcels_btn.setEnabled(False)
        layout.addWidget(self.import_parcels_btn)

        self.import_buildings_btn = QPushButton("Import Buildings From File")
        self.import_buildings_btn.setEnabled(False)
        layout.addWidget(self.import_buildings_btn)

        self.validate_btn = QPushButton("Validate Project")
        self.validate_btn.setEnabled(False)
        layout.addWidget(self.validate_btn)

        self.export_btn = QPushButton("Export Final ZIP")
        self.export_btn.setEnabled(False)
        layout.addWidget(self.export_btn)

        self.setLayout(layout)

        self.load_zip_btn.clicked.connect(self.load_zip)
        self.open_session_btn.clicked.connect(self.open_existing_session_action)
        self.save_session_btn.clicked.connect(self.save_session)
        self.open_layers_btn.clicked.connect(self.open_layers_from_zip)
        self.create_session_btn.clicked.connect(self.create_persistent_session)
        self.import_parcels_btn.clicked.connect(self.import_parcels_action)
        self.import_buildings_btn.clicked.connect(self.import_buildings_action)
        self.validate_btn.clicked.connect(self.validate_layers)
        self.export_btn.clicked.connect(self.export_zip)

    def _update_info(self, text):
        self.info_label.setText(text)

    def _disable_qgis_default_form(self, layer):
        if layer is None or not layer.isValid():
            return
        try:
            cfg = layer.editFormConfig()
            cfg.setSuppress(QgsEditFormConfig.SuppressOn)
            layer.setEditFormConfig(cfg)
        except Exception:
            pass

    def _select_validation_errors(self, result):
        if not self.session_info:
            return

        parcels_layer = self.session_info.get("parcels_layer")
        buildings_layer = self.session_info.get("buildings_layer")

        if parcels_layer:
            parcels_layer.removeSelection()
        if buildings_layer:
            buildings_layer.removeSelection()

        parcel_ids = set()
        building_ids = set()

        for err in result["parcels"]["errors"]:
            fid = err.get("feature_id", -1)
            if fid is not None and fid >= 0:
                parcel_ids.add(fid)

        for err in result["buildings"]["errors"]:
            fid = err.get("feature_id", -1)
            if fid is not None and fid >= 0:
                building_ids.add(fid)

        if parcels_layer and parcel_ids:
            parcels_layer.selectByIds(list(parcel_ids))

        if buildings_layer and building_ids:
            buildings_layer.selectByIds(list(building_ids))

        try:
            from qgis.utils import iface as iface_obj
        except Exception:
            iface_obj = None

        if iface_obj:
            if parcels_layer and parcel_ids:
                bbox = parcels_layer.boundingBoxOfSelected()
                if bbox and not bbox.isEmpty():
                    iface_obj.mapCanvas().setExtent(bbox)
                    iface_obj.mapCanvas().refresh()
            elif buildings_layer and building_ids:
                bbox = buildings_layer.boundingBoxOfSelected()
                if bbox and not bbox.isEmpty():
                    iface_obj.mapCanvas().setExtent(bbox)
                    iface_obj.mapCanvas().refresh()

    def _apply_parcel_auto_logic(self, layer, fid):
        feat = layer.getFeature(fid)
        if not feat.isValid():
            return

        geom = feat.geometry()
        if geom is None or geom.isEmpty():
            return

        attrs = initial_values_for_new_parcel(layer, feat)

        layer.beginEditCommand("Parcel auto logic")
        self._suppress_parcel_events = True
        try:
            if attrs:
                apply_attributes(layer, fid, attrs)

            enforce_no_overlap_same_layer(layer, fid)
            update_area_for_all_features(layer)

            parcels_layer = self.session_info["parcels_layer"]
            buildings_layer = self.session_info["buildings_layer"]
            sync_buildings_parcno(parcels_layer, buildings_layer)

        finally:
            self._suppress_parcel_events = False
            layer.endEditCommand()

    def _on_parcel_feature_added(self, layer, fid):
        if self._suppress_parcel_events:
            return
        QTimer.singleShot(0, lambda: self._apply_parcel_auto_logic(layer, fid))

    def _on_parcel_geometry_changed(self, layer, fid):
        if self._suppress_parcel_events:
            return
        QTimer.singleShot(0, lambda: self._apply_parcel_auto_logic(layer, fid))

    def _apply_building_auto_logic(self, layer, fid):
        feat = layer.getFeature(fid)
        if not feat.isValid():
            return

        geom = feat.geometry()
        if geom is None or geom.isEmpty():
            return

        parcels_layer = self.session_info["parcels_layer"] if self.session_info else None
        attrs = initial_values_for_new_building(layer, parcels_layer, feat)

        layer.beginEditCommand("Building auto logic")
        self._suppress_building_events = True
        try:
            if attrs:
                apply_attributes(layer, fid, attrs)

            enforce_no_overlap_same_layer(layer, fid)
            update_area_for_all_features(layer)
            sync_buildings_parcno(
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"]
            )

        finally:
            self._suppress_building_events = False
            layer.endEditCommand()

    def _on_building_feature_added(self, layer, fid):
        if self._suppress_building_events:
            return
        QTimer.singleShot(0, lambda: self._apply_building_auto_logic(layer, fid))

    def _on_building_geometry_changed(self, layer, fid):
        if self._suppress_building_events:
            return
        QTimer.singleShot(0, lambda: self._apply_building_auto_logic(layer, fid))

    def _connect_parcel_layer(self, layer):
        if layer is None or not layer.isValid():
            return
        if layer.id() in self._parcel_connected:
            return

        self._disable_qgis_default_form(layer)
        layer.featureAdded.connect(lambda fid, lyr=layer: self._on_parcel_feature_added(lyr, fid))
        layer.geometryChanged.connect(lambda fid, geom, lyr=layer: self._on_parcel_geometry_changed(lyr, fid))
        self._parcel_connected.add(layer.id())

    def _connect_building_layer(self, layer):
        if layer is None or not layer.isValid():
            return
        if layer.id() in self._building_connected:
            return

        self._disable_qgis_default_form(layer)
        layer.featureAdded.connect(lambda fid, lyr=layer: self._on_building_feature_added(lyr, fid))
        layer.geometryChanged.connect(lambda fid, geom, lyr=layer: self._on_building_geometry_changed(lyr, fid))
        self._building_connected.add(layer.id())

    def _attach_managed_fields(self):
        if not self.session_info:
            return

        parcels_layer = self.session_info.get("parcels_layer")
        buildings_layer = self.session_info.get("buildings_layer")

        self.managed_fields.attach_layers(parcels_layer, buildings_layer)
        update_area_for_all_features(parcels_layer)
        update_area_for_all_features(buildings_layer)

        self._connect_parcel_layer(parcels_layer)
        self._connect_building_layer(buildings_layer)

    def _enable_edit_buttons(self, enabled=True):
        self.save_session_btn.setEnabled(enabled and self.session_info is not None)
        self.import_parcels_btn.setEnabled(enabled and self.session_info is not None)
        self.import_buildings_btn.setEnabled(enabled and self.session_info is not None)
        self.validate_btn.setEnabled(enabled and self.session_info is not None)

    def import_parcels_action(self):
        if not self.session_info:
            QMessageBox.warning(self, "No Session", "Open editable layers or session first.")
            return

        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Import Parcels From File",
            "",
            "Vector files (*.shp *.gpkg *.geojson *.json *.kml);;All files (*.*)"
        )
        if not file_path:
            return

        try:
            stats = import_parcels_from_file(
                file_path,
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"],
                update_area_for_all_features,
            )

            msg = f"Imported parcels: {stats['imported']}\nSkipped: {stats['skipped']}"
            if stats["messages"]:
                msg += "\n\n" + "\n".join(stats["messages"][:20])

            self._update_info(msg)
            QMessageBox.information(self, "Parcel Import", msg)

        except Exception as e:
            QMessageBox.critical(self, "Parcel Import Error", str(e))

    def import_buildings_action(self):
        if not self.session_info:
            QMessageBox.warning(self, "No Session", "Open editable layers or session first.")
            return

        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Import Buildings From File",
            "",
            "Vector files (*.shp *.gpkg *.geojson *.json *.kml);;All files (*.*)"
        )
        if not file_path:
            return

        try:
            stats = import_buildings_from_file(
                file_path,
                self.session_info["buildings_layer"],
                self.session_info["parcels_layer"],
                update_area_for_all_features,
            )

            msg = f"Imported buildings: {stats['imported']}\nSkipped: {stats['skipped']}"
            if stats["messages"]:
                msg += "\n\n" + "\n".join(stats["messages"][:20])

            self._update_info(msg)
            QMessageBox.information(self, "Building Import", msg)

        except Exception as e:
            QMessageBox.critical(self, "Building Import Error", str(e))

    def load_zip(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Select Cadastre ZIP",
            "",
            "ZIP files (*.zip)"
        )

        if not file_path:
            return

        try:
            self.package = load_cadastre_zip(file_path)
            self.session_info = None
            self.validation_result = None

            self.open_layers_btn.setEnabled(True)
            self.create_session_btn.setEnabled(False)
            self._enable_edit_buttons(False)
            self.export_btn.setEnabled(False)

            self._update_info(
                f"Loaded ZIP: {file_path}\n"
                f"Extracted to: {self.package.extract_dir}\n"
                "Next step: Open Editable Layers From ZIP"
            )

            QMessageBox.information(self, "ZIP Loaded", "ZIP loaded successfully.")

        except Exception as e:
            self.package = None
            self.session_info = None
            self.validation_result = None

            self.open_layers_btn.setEnabled(False)
            self.create_session_btn.setEnabled(False)
            self._enable_edit_buttons(False)
            self.export_btn.setEnabled(False)

            QMessageBox.critical(self, "Load Error", str(e))
            self._update_info("Failed to load ZIP.")

    def open_layers_from_zip(self):
        if not self.package:
            QMessageBox.warning(self, "No ZIP", "Load a source ZIP first.")
            return

        parcels_path = self.package.get_path("parcels_o.gml")
        buildings_path = self.package.get_path("buildings_o.gml")

        try:
            self.session_info = build_session_layers(parcels_path, buildings_path)
            self.validation_result = None

            parcels_layer = self.session_info["parcels_layer"]
            buildings_layer = self.session_info["buildings_layer"]

            QgsProject.instance().addMapLayer(parcels_layer)
            QgsProject.instance().addMapLayer(buildings_layer)

            self._attach_managed_fields()

            pstats = self.session_info["parcels_stats"]
            bstats = self.session_info["buildings_stats"]

            self.create_session_btn.setEnabled(True)
            self._enable_edit_buttons(True)
            self.export_btn.setEnabled(False)

            self._update_info(
                "Editable layers created from ZIP.\n\n"
                "Workspace: temporary memory layers\n"
                "CRS: EPSG:6316\n"
                f"Parcels added: {pstats['added']}, skipped: {pstats['skipped']}\n"
                f"Buildings added: {bstats['added']}, skipped: {bstats['skipped']}\n"
                "Forms: disabled\n"
                "Duplicates: allowed during editing\n"
                "Validation happens before export\n"
                "AREA = auto-calculated\n"
            )

            QMessageBox.information(self, "Layers Opened", "Editable layers were created successfully.")

        except Exception as e:
            self.session_info = None
            self.validation_result = None
            self._enable_edit_buttons(False)
            self.export_btn.setEnabled(False)
            QMessageBox.critical(self, "Open Layers Error", str(e))

    def create_persistent_session(self):
        if not self.package:
            QMessageBox.warning(self, "No ZIP", "Load a source ZIP first.")
            return

        if not self.session_info:
            QMessageBox.warning(self, "No Layers", "Open editable layers first.")
            return

        session_dir = QFileDialog.getExistingDirectory(self, "Select or create session folder")
        if not session_dir:
            return

        try:
            created = create_session_from_layers(
                self.package,
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"],
                session_dir
            )

            QgsProject.instance().removeMapLayer(self.session_info["parcels_layer"].id())
            QgsProject.instance().removeMapLayer(self.session_info["buildings_layer"].id())

            self.session_info = created
            self.validation_result = None

            QgsProject.instance().addMapLayer(self.session_info["parcels_layer"])
            QgsProject.instance().addMapLayer(self.session_info["buildings_layer"])

            self._attach_managed_fields()

            self._enable_edit_buttons(True)
            self.export_btn.setEnabled(False)

            self._update_info(
                f"Persistent session created.\n"
                f"Session folder: {self.session_info['session_dir']}\n"
                f"GPKG: {self.session_info['gpkg_path']}\n"
                "Forms: disabled\n"
                "Duplicates: allowed during editing\n"
                "Validation happens before export\n"
            )

            QMessageBox.information(self, "Session Created", "Persistent session created successfully.")

        except Exception as e:
            QMessageBox.critical(self, "Session Error", str(e))

    def open_existing_session_action(self):
        session_dir = QFileDialog.getExistingDirectory(self, "Select existing session folder")
        if not session_dir:
            return

        try:
            opened = open_existing_session(session_dir)

            self.session_info = opened
            self.package = build_package_from_session_files(
                opened["files"],
                extract_dir=opened["source_dir"]
            )
            self.validation_result = None

            QgsProject.instance().addMapLayer(self.session_info["parcels_layer"])
            QgsProject.instance().addMapLayer(self.session_info["buildings_layer"])

            self._attach_managed_fields()

            self._enable_edit_buttons(True)
            self.export_btn.setEnabled(False)
            self.open_layers_btn.setEnabled(False)
            self.create_session_btn.setEnabled(False)

            self._update_info(
                f"Session opened successfully.\n"
                f"Session folder: {self.session_info['session_dir']}\n"
                f"GPKG: {self.session_info['gpkg_path']}\n"
                "Forms: disabled\n"
                "Duplicates: allowed during editing\n"
                "Validation happens before export\n"
            )

            QMessageBox.information(self, "Session Opened", "Existing session opened successfully.")

        except Exception as e:
            QMessageBox.critical(self, "Open Session Error", str(e))

    def save_session(self):
        if not self.session_info:
            QMessageBox.warning(self, "No Session", "There is no persistent session to save.")
            return

        try:
            update_area_for_all_features(self.session_info["parcels_layer"])
            update_area_for_all_features(self.session_info["buildings_layer"])

            sync_buildings_parcno(
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"]
            )

            save_session_layers(self.session_info)
            self._attach_managed_fields()

            QMessageBox.information(self, "Session Saved", "Session saved successfully.")
        except Exception as e:
            QMessageBox.critical(self, "Save Session Error", str(e))

    def validate_layers(self):
        if not self.session_info:
            QMessageBox.warning(self, "No Layers", "Open editable layers first.")
            return

        try:
            update_area_for_all_features(self.session_info["parcels_layer"])
            update_area_for_all_features(self.session_info["buildings_layer"])

            sync_buildings_parcno(
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"]
            )

            parcels_layer = self.session_info["parcels_layer"]
            buildings_layer = self.session_info["buildings_layer"]

            self.validation_result = validate_project(parcels_layer, buildings_layer)
            report_text = format_validation_report(self.validation_result)

            self._update_info(report_text)
            self._select_validation_errors(self.validation_result)

            if self.validation_result["ok"]:
                self.export_btn.setEnabled(True)
                QMessageBox.information(self, "Validation Successful", report_text)
            else:
                self.export_btn.setEnabled(False)
                QMessageBox.warning(self, "Validation Failed", report_text)

        except Exception as e:
            self.validation_result = None
            self.export_btn.setEnabled(False)
            QMessageBox.critical(self, "Validation Error", str(e))

    def export_zip(self):
        if not self.package:
            QMessageBox.warning(self, "No ZIP", "Load a ZIP or open an existing session first.")
            return

        if not self.session_info:
            QMessageBox.warning(self, "No Layers", "Open editable layers first.")
            return

        if not self.validation_result or not self.validation_result["ok"]:
            QMessageBox.warning(self, "Validation Required", "Validate project successfully before export.")
            return

        output_zip_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save Final ZIP",
            "",
            "ZIP files (*.zip)"
        )

        if not output_zip_path:
            return

        if not output_zip_path.lower().endswith(".zip"):
            output_zip_path += ".zip"

        try:
            update_area_for_all_features(self.session_info["parcels_layer"])
            update_area_for_all_features(self.session_info["buildings_layer"])

            sync_buildings_parcno(
                self.session_info["parcels_layer"],
                self.session_info["buildings_layer"]
            )

            export_final_zip(self.package, self.session_info, output_zip_path)
            QMessageBox.information(
                self,
                "Export Successful",
                f"Final ZIP exported successfully:\n{output_zip_path}"
            )
        except Exception as e:
            QMessageBox.critical(self, "Export Error", str(e))