from typing import Optional

from qgis.core import (
    Qgis,
    QgsMapLayer,
    QgsMessageLog,
    QgsProcessingContext,
    QgsProcessingFeedback,
    QgsProject,
    QgsReadWriteContext,
    QgsVectorLayer,
)
from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtWidgets import (
    QMessageBox,
    QProgressDialog,
)
from qgis.PyQt.QtXml import QDomDocument
from qgis.utils import iface

import processing

from ... import i18n
from ..error_handler import handle_api_error
from ...kumoy import api, constants
from ...kumoy.api.error import format_api_error
from ...pyqt_version import (
    QDIALOG_CODE,
    QT_APPLICATION_MODAL,
    exec_dialog,
)
from ..dialog_layer_select import LayerSelectDialog
from ..utils import get_local_vector_layers


def on_convert_to_kumoy_clicked(layer: QgsVectorLayer, project_id: str) -> None:
    # Validate layer before proceeding
    if not layer or not layer.isValid():
        QMessageBox.warning(
            None,
            i18n.tr("Invalid Layer"),
            i18n.tr("The selected layer is no longer valid or has been removed."),
        )
        return

    layer_name = layer.name()

    if not project_id:
        QMessageBox.warning(
            None,
            i18n.tr("No Project Selected"),
            i18n.tr("Please select a Kumoy project before converting a layer."),
        )
        return

    success, error = convert_to_kumoy(layer, project_id)

    if success:
        iface.messageBar().pushMessage(
            constants.PLUGIN_NAME,
            i18n.tr("Layer '{}' converted to Kumoy successfully.").format(layer_name),
            level=Qgis.Success,
            duration=5,
        )
    else:
        QMessageBox.warning(
            None,
            i18n.tr("Conversion Failed"),
            i18n.tr("Failed to convert layer '{}' to Kumoy:\n{}").format(
                layer_name, error
            ),
        )


def convert_local_layers(
    project_id: str,
) -> tuple[bool, list[tuple[str, str]]]:
    """Prompt user to select and convert local layers.

    Args:
        project_id: Project ID to convert layers to

    Returns:
        tuple: (cancelled, conversion_errors)
            cancelled: True if user cancelled (map save should be aborted)
            conversion_errors: list of (layer_name, error_message) for failed conversions
    """

    # Get local layers in layer panel order
    local_layers = get_local_vector_layers()

    if not local_layers:
        return (False, [])

    # Get quota info to determine max selectable layers
    try:
        project = api.project.get_project(project_id)
        org_id = project.team.organization.id
        org_detail = api.organization.get_organization(org_id)
        plan_limits = api.plan.get_plan_limits(
            org_detail.subscriptionPlan, org_detail.storageUnits
        )
    except Exception as e:
        handle_api_error(
            e, parent=None, log_prefix=i18n.tr("Failed to check layer limits")
        )
        return (True, [])

    current_vector_count = org_detail.usage.vectors

    # Show layer selection dialog
    dialog = LayerSelectDialog(
        local_layers,
        plan_limits.maxVectors,
        current_vector_count,
    )
    if exec_dialog(dialog) != QDIALOG_CODE.Accepted:
        return (True, [])

    selected_layers = dialog.selected_layers
    if not selected_layers:
        return (False, [])

    # Convert selected layers
    conversion_errors = []
    for layer in selected_layers:
        success, error = convert_to_kumoy(layer, project_id)
        if not success:
            conversion_errors.append((layer.name(), error))

    iface.mapCanvas().refresh()

    return (False, conversion_errors)


def convert_to_kumoy(
    layer: QgsVectorLayer, project_id: str
) -> tuple[bool, Optional[str]]:
    """Convert a vector layer to Kumoy
    Returns:
        tuple: (success: bool, error_message: str or None)
    """

    # Validate layer before proceeding
    if not layer or not layer.isValid():
        return (False, i18n.tr("The layer is no longer valid or has been removed."))

    progress_dialog = None

    try:
        vector_name = layer.name()
        # trim name if too long
        if len(vector_name) > constants.MAX_CHARACTERS_VECTOR_NAME:
            vector_name = vector_name[: constants.MAX_CHARACTERS_VECTOR_NAME]

        # Create progress dialog
        progress_dialog = QProgressDialog(
            i18n.tr("Uploading layer '{}'...").format(vector_name),
            i18n.tr("Cancel"),
            0,
            100,
            iface.mainWindow(),
        )
        progress_dialog.setWindowTitle(i18n.tr("Kumoy Upload"))
        progress_dialog.setWindowModality(QT_APPLICATION_MODAL)
        progress_dialog.setMinimumDuration(0)
        progress_dialog.setValue(0)
        progress_dialog.show()

        # Issue #356: ensure dialog is drawn properly on Windows
        progress_dialog.repaint()
        QCoreApplication.processEvents()
        progress_dialog.repaint()
        QCoreApplication.processEvents()

        # Create processing context and feedback
        context = QgsProcessingContext()
        feedback = QgsProcessingFeedback()

        # Connect feedback to progress dialog
        def update_progress(progress):
            if progress_dialog:
                progress_dialog.setValue(int(progress))

        feedback.progressChanged.connect(update_progress)

        # Handle cancel
        progress_dialog.canceled.connect(feedback.cancel)

        # Get the project index for the processing algorithm using project id
        organizations = api.organization.get_organizations()
        all_projects = []
        for org in organizations:
            org_projects = api.project.get_projects_by_organization(org.id)
            all_projects.extend(org_projects)

        # Find the index of current project
        project_index = None
        for idx, proj in enumerate(all_projects):
            if proj.id == project_id:
                project_index = idx
                break

        if project_index is None:
            raise Exception(i18n.tr("Project not found in organization list"))

        # Run the upload algorithm
        result = processing.run(
            "kumoy:uploadvector",
            {
                "INPUT": layer,
                "PROJECT": project_index,
                "VECTOR_NAME": vector_name,
                "SELECTED_FIELDS": [],
            },
            context=context,
            feedback=feedback,
        )

        # Check if cancelled
        if feedback.isCanceled():
            progress_dialog.close()
            iface.messageBar().pushMessage(
                constants.PLUGIN_NAME,
                i18n.tr("Upload cancelled"),
                level=Qgis.Warning,
                duration=3,
            )
            return (False, i18n.tr("Upload cancelled by user"))

        if not result or "VECTOR_ID" not in result:
            raise Exception(i18n.tr("Upload failed - unable to get vector id"))

        vector_id = result["VECTOR_ID"]

        progress_dialog.close()
        progress_dialog = None

        # Get updated vector details
        vector = api.vector.get_vector(vector_id)

        # Create Kumoy layer URI
        vector_uri = f"project_id={vector.projectId};vector_id={vector.id};vector_name={vector.name};vector_type={vector.type};"

        # Create the layer
        kumoy_layer = QgsVectorLayer(
            vector_uri, vector.name, constants.DATA_PROVIDER_KEY
        )

        if kumoy_layer.isValid():
            # Configure kumoy_id as read-only
            field_idx = kumoy_layer.fields().indexOf("kumoy_id")
            if field_idx >= 0:
                config = kumoy_layer.editFormConfig()
                config.setReadOnly(field_idx, True)
                kumoy_layer.setEditFormConfig(config)

            # Copy layer style from original layer
            _copy_layer_style(layer, kumoy_layer)

            # Get original layer position in legend
            root = QgsProject.instance().layerTreeRoot()
            original_layer_node = root.findLayer(layer.id())

            if original_layer_node:
                # Replace local layer by new Kumoy layer at the same index position
                parent_node = original_layer_node.parent()
                index = parent_node.children().index(original_layer_node)

                QgsProject.instance().addMapLayer(kumoy_layer, False)
                parent_node.insertLayer(index, kumoy_layer)
                parent_node.removeChildNode(original_layer_node)
                QgsProject.instance().removeMapLayer(layer.id())

                # Set the new layer as the current/selected layer
                layer_tree_view = iface.layerTreeView()
                new_layer_node = root.findLayer(kumoy_layer.id())
                if new_layer_node:
                    layer_tree_view.setCurrentLayer(kumoy_layer)
            else:
                # Fallback: add to root if original node not found
                QgsProject.instance().addMapLayer(kumoy_layer)
                QgsProject.instance().removeMapLayer(layer.id())

                # Set as current layer
                iface.layerTreeView().setCurrentLayer(kumoy_layer)

        else:
            error_msg = (
                kumoy_layer.error().message()
                if kumoy_layer.error()
                else "Unknown error"
            )
            raise Exception(
                i18n.tr("Failed to create Kumoy layer: {}").format(error_msg)
            )

        # Success
        return (True, None)

    except Exception as e:
        if progress_dialog:
            progress_dialog.close()

        error_msg = format_api_error(e)
        QgsMessageLog.logMessage(
            f"Error converting layer: {error_msg}",
            constants.LOG_CATEGORY,
            Qgis.Critical,
        )
        return (False, error_msg)


def _copy_layer_style(
    source_layer: QgsVectorLayer, target_layer: QgsVectorLayer
) -> None:
    """Copy style from source layer to target layer"""
    doc = QDomDocument()
    elem = doc.createElement("qgis")
    doc.appendChild(elem)
    context = QgsReadWriteContext()

    source_layer.writeStyle(elem, doc, "", context, QgsMapLayer.AllStyleCategories)
    target_layer.readStyle(elem, "", context, QgsMapLayer.AllStyleCategories)
    target_layer.triggerRepaint()
