import os
import traceback
import logging
from typing import Optional


from PySide2.QtCore import QFileInfo
from PySide2.QtGui import QIcon
from PySide2.QtSql import QSqlQuery
from PySide2.QtWidgets import QDialog

from axipy import (get_database, task_manager, Position, ObserverManager, CoordSystem,
                   Table, provider_manager, Feature, Schema, Notifications,
                   data_manager, TypeSqlDialect, Style, view_manager, MapView,
                   Attribute, Layer, Map, Plugin, Separator, DialogTask, Attribute, Schema, ActionButton)
from .CoordinateProcess import (CoordinateWriter, FormatCoordinateData, TypeCoordinatesData, CoordinateReader,
                                CoordinateProcess)
from .LoadPointsDialog import LoadPointsLastUsedData, TypeResultAction, LoadPointsDialog
from .SavePointsDialog import SavePointsDialog


def ensure_sql_init_for_bg_thread():
    query = QSqlQuery(get_database())
    task_manager.run_in_gui(lambda: query.exec_("Select 'Init SQL Engine'"))


class GeomFromTable(Plugin):

    def __init__(self) -> None:
        self._separator: Separator = None
        self.load_tool: ActionButton = None
        self.save_tool: ActionButton = None
        self._last_coordsystem: CoordSystem = None
        self._last_used: LoadPointsLastUsedData = None

    def load(self) -> None:
        position = Position('Таблица', 'Создание')
        self._separator = Separator()
        position.add(self._separator)
        self.load_tool = self.create_action(
            self.tr('Создать из узлов'),
            icon=self._get_icon('open.png'),
            on_click=self.load_points,
            enable_on=ObserverManager.HasTables,
            tooltip=self.tr(
                'Создание геометрии из таблицы с колонками координатами узлов.\n'
                'Поддерживается создание полилиний/полигонов из последовательности узлов '
                'или по направлению и расстоянию (обходу)'),
            doc_file='index.html#load')
        position.add(self.load_tool)
        self.save_tool = self.create_action(
            self.tr('Сохранить как узлы'),
            icon=self._get_icon('save.png'),
            on_click=self.save_points,
            enable_on=ObserverManager.HasTables,
            tooltip=self.tr(
                'Сохраняет узлы объектов в виде таблицы с колонками координат (X, Y) '
                'и их последовательностью или в виде обхода'),
            doc_file='index.html#unload')
        position.add(self.save_tool)

        self._last_coordsystem = CoordSystem.current()
        self._last_used = LoadPointsLastUsedData()

    @staticmethod
    def _get_icon(filename: str) -> QIcon:
        return QIcon(os.path.join(os.path.dirname(__file__), filename))

    def unload(self) -> None:
        self.save_tool.remove()
        self.load_tool.remove()
        self._separator.remove()

    def _save_points(self, task: DialogTask, table_in: Table, cw: CoordinateWriter, result_file_name: str) -> None:
        ensure_sql_init_for_bg_thread()
        form = cw.FormatCoordinates
        tp = cw.TypeCoordinates
        task.message = 'Создание структуры таблицы'

        suffix = QFileInfo(result_file_name).suffix().lower()

        def attr_coord(name) -> Attribute:
            if form == FormatCoordinateData.simple and tp != TypeCoordinatesData.rumb:
                return Attribute.float(name)
            return Attribute.string(name, CoordinateWriter.LENGTH_STR_FIELD)
            # return attr.float(name) if form == FormatCoordinateData.simple
            # else: attr.string(name, CoordinateWriter.LENGTH_STR_FIELD)

        # Через экспорт из временную таблицу для 'csv' и 'xlsx'
        definition = {
            'src': result_file_name if suffix not in ['csv', 'xlsx'] else '',
            'schema': Schema(
                Attribute.integer(CoordinateProcess.IdFeatureField),
                Attribute.integer(CoordinateProcess.IdGeometryField),
                Attribute.integer(CoordinateProcess.IdPointField),
                attr_coord(CoordinateProcess.ValueFirstField),
                attr_coord(CoordinateProcess.ValueSecondField)
            ),
            'hidden': True,
            'override': True
        }
        table_out = provider_manager.create(definition)  # type: Table
        task.range = task.Range(0, table_in.count())
        task.message = 'Вставка данных'
        try:
            table_out.insert((Feature({CoordinateProcess.IdFeatureField: v[0],
                                       CoordinateProcess.IdGeometryField: v[1],
                                       CoordinateProcess.IdPointField: v[2],
                                       CoordinateProcess.ValueFirstField: v[3],
                                       CoordinateProcess.ValueSecondField: v[4]})
                              for v in cw.write(table_in.items(), task)
                              ))
            task.message = 'Сохранение данных'
            task.infinite_progress = True
            if suffix == 'csv':
                destination = provider_manager.csv.get_destination(result_file_name, Schema(), encoding='cp1251')
                destination.export_from_table(table_out, copy_schema=True)
            elif suffix == 'xlsx':
                destination = provider_manager.excel.get_destination(result_file_name, Schema())
                destination.export_from_table(table_out, copy_schema=True)
            else:
                table_out.commit()
                table_out.close()
            Notifications.push(self.tr('Информация'),
                               self.tr(f'Данные сохранены. Путь к файлу:  {result_file_name}'))
        except RuntimeError:
            traceback.print_exc()
            if task.is_canceled:
                Notifications.push(self.tr('Предупреждение'), self.tr('Процесс прерван пользователем.'),
                                   Notifications.Warning)
            else:
                Notifications.push(self.tr('Ошибка'), self.tr('Процесс завершен неудачно.'),
                                   Notifications.Critical)
            table_out.rollback()
            table_out.close()

    def save_points(self) -> None:
        logging.debug('--save points--')
        # Сохранить геометрию в виде табличных данных
        fd = SavePointsDialog(self)
        fd._ui.setWindowTitle(self.save_tool.action.text())
        if fd.exec() == QDialog.Accepted:
            cw = CoordinateWriter()
            cw.TypeCoordinates = TypeCoordinatesData(fd.result_type_coordinates_index())
            cw.FormatCoordinates = FormatCoordinateData(fd.result_format_data_index())
            cw.Precision = fd.result_precision()
            dialog_task = DialogTask(self._save_points, cancelable=True)
            dialog_task.title = 'Экспорт данных'
            dialog_task.run_and_get(fd.result_table(),
                cw,
                fd.result_file_name())
            
    def _load_points_prepare_data(self, task: DialogTask, qry_text) -> Table:
        return data_manager.query_hidden(qry_text, dialect=TypeSqlDialect.sqlite)

    def _load_points(self, task: DialogTask, reader: CoordinateReader, qry: Table, record_count: int, data_object: Table, coordsystem: CoordSystem) -> None:
        task.range = task.Range(0, record_count)
        task.message = 'Чтение данных'
        for id_, g in reader.read(qry.items(), task, coordsystem):
            try:
                feature = Feature({'id': id_}, geometry=g, style=Style.for_geometry(g))
                data_object.insert(feature)
            except Exception as e:
                logging.debug(f'ERROR: {e}')
        task.message = 'Сохранение'
        data_object.commit()
        if task.is_canceled:
            Notifications.push(self.tr('Предупреждение'), self.tr('Процесс прерван пользователем.'),
                              Notifications.Warning)
        else:
            Notifications.push(self.tr('Информация'), self.tr('Данные загружены.'))

    def load_points(self) -> None:
        logging.debug('--load points--')
        data_object = None
        cs = view_manager.active.coordsystem if isinstance(view_manager.active, MapView) else self._last_coordsystem
        fd = LoadPointsDialog(self, cs, self._last_used)
        fd._ui.setWindowTitle(self.load_tool.action.text())
        if fd.exec() == QDialog.Accepted:
            if fd.result_type_action() == TypeResultAction.current_layer:
                if isinstance(view_manager.active, MapView):
                    edit_layer = view_manager.active.map.editable_layer
                    if edit_layer is not None:
                        data_object = edit_layer.data_object
            if fd.result_type_action() == TypeResultAction.new_table:
                schema = Schema(
                    Attribute.string('id'),
                    coordsystem=fd.result_coordsystem()
                )
                definition = {"src": fd.result_filename(), 
                              "schema": schema, 
                              "override": True}
                data_object = provider_manager.create(definition)

            self._last_coordsystem = fd.result_coordsystem()
            self._last_used = fd.last_used_values()
            cr = CoordinateReader()
            cr.IsClosedPolygon = fd.result_is_closed_polygon()
            cr.IsClosedLinear = fd.result_is_closed_linear()
            cr.HasNumber = fd.result_has_number()
            cr.HasPartNumber = fd.result_has_part_number()
            cr.HasPointNumber = fd.result_has_point_number()
            cr.TypeCoordinates = fd.result_type_coordinates()
            query_text = fd.result_sql_text()
            records = fd.result_record_count()
            is_valid, msg = data_manager.check_query(query_text)

            if not is_valid:
                raise Exception(f"{query_text}\n{msg}")
            
            dialog_task_prepare = DialogTask(self._load_points_prepare_data, cancelable=False)
            dialog_task_prepare.message = 'Подготовка данных'
            qry = dialog_task_prepare.run_and_get(query_text)

            dialog_task = DialogTask(self._load_points, cancelable=True)
            dialog_task.title = 'Импорт данных'
            dialog_task.run_and_get(cr,
                qry,
                records,
                data_object,
                fd.result_coordsystem())

            if fd.result_type_action() == TypeResultAction.new_table and data_object:
                layer = Layer.create(data_object)
                map_ = Map([layer])
                view_manager.create_mapview(map_)
