import axipy
import time
from PySide2.QtWidgets import (QWizard, QWizardPage, QVBoxLayout, QLabel,
                               QListWidget, QListWidgetItem, QCheckBox,
                               QTextEdit, QProgressBar)
from PySide2.QtCore import Qt, Slot, Signal
from PySide2.QtGui import QPixmap

from pathlib import Path
import itertools
from typing import Optional

def _find_existing_dat(source_path: str) -> Optional[str]:
    path = Path(source_path)
    if path.suffix.lower() != '.tab':
        return None

    # Перебираем все 8 вариантов регистра расширения dat
    for chars in itertools.product('dD', 'aA', 'tT'):
        candidate = path.with_suffix(f'.{"".join(chars)}')
        if candidate.exists():
            return str(candidate)
            
    return None

def _has_deleted_raw(task: axipy.Task, dbf_path: str):
    with open(dbf_path, "rb") as f:
        header = f.read(32)
        record_len = int.from_bytes(header[10:12], "little")
        header_size = int.from_bytes(header[8:10], "little")

        f.seek(header_size)
        count = 0
        while True:
            byte = f.read(1)
            if not byte or byte == b"\x1a":
                break
            count += 1
            if count % 1000 == 0:
                task.raise_if_canceled()
            if byte == b"*":
                return True
            f.seek(record_len - 1, 1)
    return False
    

def _search_task(task: axipy.Task, tables: list) -> str:
    """Фоновая задача проверки таблиц."""
    task.range = task.Range(0, len(tables))
    task.message = "Поиск неупакованных таблиц..."

    res_unpacked = []
    
    for i, table in enumerate(tables):
        task.raise_if_canceled()
        dat = _find_existing_dat(axipy.data_manager[table].properties['fileName'])
        res = _has_deleted_raw(task, dat)
        if res:
            res_unpacked.append(table)
        task.value = i + 1
    
    if res_unpacked:
        return "Список неупакованных таблиц:\n" + "\n".join(res_unpacked)
    else:
        return "Все таблицы упакованы"


class Page1(QWizardPage):
    """Шаг выбора таблиц."""
    def __init__(self, tables, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)
        layout.addWidget(QLabel("Выберите таблицу"))

        self.select_all = QCheckBox("Выбрать все")
        self.select_all.setChecked(True)
        layout.addWidget(self.select_all)

        self.list_widget = QListWidget()
        for t in tables:
            item = QListWidgetItem(t)
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
            self.list_widget.addItem(item)
        layout.addWidget(self.list_widget)

        self.select_all.stateChanged.connect(self._on_select_all)
        self.list_widget.itemChanged.connect(self._on_item_changed)

    def _on_select_all(self, state):
        self.list_widget.blockSignals(True)
        check_state = Qt.Checked if state == Qt.Checked else Qt.Unchecked
        for i in range(self.list_widget.count()):
            self.list_widget.item(i).setCheckState(check_state)
        self.list_widget.blockSignals(False)
        self.completeChanged.emit()

    def _on_item_changed(self, item):
        self.select_all.blockSignals(True)
        all_checked = all(
            self.list_widget.item(i).checkState() == Qt.Checked
            for i in range(self.list_widget.count())
        )
        self.select_all.setCheckState(Qt.Checked if all_checked else Qt.Unchecked)
        self.select_all.blockSignals(False)
        self.completeChanged.emit()

    def get_selected(self) -> list:
        return [
            self.list_widget.item(i).text()
            for i in range(self.list_widget.count())
            if self.list_widget.item(i).checkState() == Qt.Checked
        ]

    def isComplete(self) -> bool:
        # Кнопка "Поиск" активна только при выборе хотя бы одной таблицы
        return len(self.get_selected()) > 0


class Page2(QWizardPage):
    """Шаг отображения результата."""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setTitle("Результат")
        layout = QVBoxLayout(self)

        self.result_edit = QTextEdit()
        self.result_edit.setReadOnly(True)
        layout.addWidget(self.result_edit)

        self.progress = QProgressBar()
        self.progress.setFixedHeight(10)
        self.progress.setTextVisible(False)
        self.progress.setVisible(False)
        layout.addWidget(self.progress)

    def initializePage(self):
        wizard = self.wizard()
        if not wizard._is_running:
            wizard.start_search()
            wizard.processing_changed.connect(self._on_processing_changed)
            self.completeChanged.emit()

    @Slot(bool)
    def _on_processing_changed(self, _):
        # Безопасный вызов completeChanged без аргументов
        self.completeChanged.emit()

    def isComplete(self) -> bool:
        # Кнопка "OK" активна только после завершения задачи
        wizard = self.wizard()
        return not wizard._is_running if wizard else True


class TableSearchWizard(QWizard):
    """Диалог поиска неупакованных таблиц."""
    processing_changed = Signal(bool)

    def __init__(self, tables, parent=None):
        parent = parent or axipy.view_manager.global_parent
        super().__init__(parent)

        self.tables = tables
        self.task = None
        self._is_running = False

        # Настройка интерфейса
        self.setWizardStyle(QWizard.ModernStyle)
        self.setPixmap(QWizard.LogoPixmap, QPixmap())
        self.setPixmap(QWizard.BannerPixmap, QPixmap())
        self.setWindowTitle("Поиск неупакованных таблиц")
        self.setOption(QWizard.NoBackButtonOnStartPage, True)
        self.setOption(QWizard.NoBackButtonOnLastPage, True)

        self.setButtonText(QWizard.NextButton, "Поиск")
        self.setButtonText(QWizard.FinishButton, "OK")
        self.setButtonText(QWizard.CancelButton, "Отмена")

        self.addPage(Page1(tables, self))
        self.addPage(Page2(self))

        # Скрываем кнопку "Назад"
        self.button(QWizard.BackButton).setVisible(False)

        # Кнопка "Отмена" активна до завершения/отмены задачи
        self.button(QWizard.CancelButton).setEnabled(True)
        self.button(QWizard.CancelButton).clicked.connect(self._on_cancel_clicked)

    def start_search(self):
        """Запуск фоновой задачи."""
        if self._is_running:
            return

        selected = self.page(0).get_selected()
        if not selected:
            return

        self._is_running = True
        self.processing_changed.emit(True)

        page2 = self.page(1)
        page2.result_edit.clear()
        page2.progress.setVisible(True)
        page2.progress.setRange(0, len(selected))
        page2.progress.setValue(0)

        self.task = axipy.Task(_search_task)
        self.task.value_changed.connect(lambda v: page2.progress.setValue(int(float(v))))
        self.task.finished.connect(self._on_task_finished)
        self.task.start(selected)

    @Slot(axipy.Task)
    def _on_task_finished(self, task_instance):
        """Обработка завершения задачи."""
        self._is_running = False
        self.processing_changed.emit(False)

        page2 = self.page(1)
        page2.progress.setVisible(False)

        if task_instance.status == axipy.Task.Status.CANCELED:
            page2.result_edit.append("Операция отменена пользователем.")
        elif task_instance.status == axipy.Task.Status.SUCCESS:
            res = task_instance.result
            page2.result_edit.setText(res if isinstance(res, str) else str(res) if res else "Готово")
        else:
            page2.result_edit.append("Задача завершилась с ошибкой:")
            try:
                res = task_instance.result
            except Exception as ex:
                page2.result_edit.append(str(ex))

        # По завершении: Отмена неактивна, OK активен
        self.button(QWizard.CancelButton).setEnabled(False)

    def _on_cancel_clicked(self):
        """Обработка нажатия кнопки Отмена."""
        if self.task and self.task.status == axipy.Task.Status.RUNNING:
            self.task.cancel()
        self.reject()


# Запуск для тестирования внутри ГИС Аксиома
if __name__ == "__main__":
    #sample_tables = ["Таблица_А", "Таблица_Б", "Таблица_В", "Таблица_Г"]
    tables = list(table.name for table in axipy.data_manager.tables if table.properties and table.properties.get('tabFileData') and table.properties['tabFileData'].get('typeSource') and table.properties.get('fileName') and _find_existing_dat(table.properties['fileName']))
    dialog = TableSearchWizard(tables)
    dialog.exec_()