from __future__ import annotations

import re
import traceback
from collections.abc import Iterable, Generator
from functools import cached_property
from typing import TYPE_CHECKING, NamedTuple

import axipy
from PySide2.QtCore import QObject, Slot
from PySide2.QtWidgets import QDialog
from osgeo import gdal
from .connection_dialog import ConnectionDialog
from .open_raster_dialog import OpenRasterDialog
from .utils import _gdal_exceptions_decor, _run_in_gui_decor

if TYPE_CHECKING:
    from .__init__ import OpenPostgreRasterPlugin


class DataSet(NamedTuple):
    table_name: str
    open_string: str
    open_string_axipy: str
    password: str


class Worker(QObject):

    def __init__(self, plugin: 'OpenPostgreRasterPlugin') -> None:
        super().__init__()
        self.plugin = plugin

        self.__d_sets: list | None = None

        self.__connection_dialog_result: ConnectionDialog.Result = ConnectionDialog.Result()
        self.__open_raster_dialog_result: OpenRasterDialog.Result = OpenRasterDialog.Result()

    @_run_in_gui_decor
    def __notify(self, message: str, type_message: axipy.Notifications) -> None:
        axipy.Notifications.push(self.plugin.title, message, type_message)

    @cached_property
    def __re_table_name(self) -> re.Pattern:
        return re.compile(r"schema='(?P<schema>.*)'\s+table='(?P<table>.*)'\s+column='(?P<column>.*)'")

    def __get_table_meta(self, open_string: str) -> tuple[str, str, str] | None:
        match = self.__re_table_name.search(open_string)
        if match:
            schema = match.group("schema")
            table = match.group("table")
            column = match.group("column")
            return schema, table, column

    def __get_table_name(self, open_string: str) -> str | None:
        meta = self.__get_table_meta(open_string)
        if meta:
            schema, table, column = meta
            return f"{schema}.{table} ({column})"

    def run_and_get(self) -> None:
        connection_dialog_ = ConnectionDialog(self.plugin, self.__connection_dialog_result)
        connection_dialog_.finished.connect(self.slot_on_connection_dialog_finished)
        connection_dialog_.open()

    @_gdal_exceptions_decor()
    def __gdal_get_sub_data_sets(self, open_string: str) -> Iterable[tuple[str, str]]:
        ds = gdal.Open(open_string)
        if not ds:
            raise RuntimeError("Can't open gdal dataset.")

        result = ds.GetSubDatasets()
        if not result:
            raise RuntimeError("Can't get sub datasets.")

        return result

    def __data_sets_gen(self, open_string: str, open_string_no_password: str, password: str) -> Generator[DataSet]:
        data_sets = self.__gdal_get_sub_data_sets(open_string)

        for data_set in data_sets:
            ds0 = data_set[0]
            table_name = self.__get_table_name(ds0)
            if table_name:
                schema, table, column = self.__get_table_meta(ds0)
                open_string_axipy = f"{open_string_no_password} schema='{schema}' table='{table}'"
                yield DataSet(table_name, ds0, open_string_axipy, password)

    @Slot(int)
    def slot_on_connection_dialog_finished(self, return_code: QDialog.DialogCode) -> None:
        if return_code != QDialog.Accepted:
            return None

        try:
            self.__d_sets = list(self.__data_sets_gen(
                self.__connection_dialog_result.connection_string,
                self.__connection_dialog_result.connection_string_no_password,
                self.__connection_dialog_result.password,
            ))
        except Exception:
            self.__notify(
                self.plugin.tr("Не удалось получить список растров. Подробности в панели 'Консоль Python'."),
                axipy.Notifications.Critical)
            traceback.print_exc()
            return None

        open_raster_dialog_ = OpenRasterDialog(self.plugin, [elem.table_name for elem in self.__d_sets],
                                               self.__open_raster_dialog_result)
        open_raster_dialog_.finished.connect(self.slot_on_open_raster_dialog_finished)
        open_raster_dialog_.open()

    @Slot(int)
    def slot_on_open_raster_dialog_finished(self, return_code: QDialog.DialogCode) -> None:
        if return_code != QDialog.Accepted:
            return None

        chosen_d_set: DataSet = self.__d_sets[self.__open_raster_dialog_result.chosen_index]

        try:
            raster = axipy.provider_manager.gdal.open(
                chosen_d_set.open_string_axipy,
                open_data={"PASSWORD": chosen_d_set.password},
            )
        except Exception:
            self.__notify(
                self.plugin.tr("Не удалось открыть выбранный растр. Подробности в панели 'Консоль Python'."),
                axipy.Notifications.Critical)
            traceback.print_exc()
            return None

        try:
            axipy.view_manager.create_mapview(raster)
        except Exception:
            self.__notify(
                self.plugin.tr("Не удалось создать карту с выбранным растром. Подробности в панели 'Консоль Python'."),
                axipy.Notifications.Critical)
            traceback.print_exc()
            return None
