from __future__ import annotations

import logging
import threading
import traceback
from collections.abc import Callable
from typing import Any

from qtpy.QtCore import QObject, QRunnable, Signal, Slot

from .models import DocumentModel
from .services import (
    DocxService,
    OCRService,
    PDFService,
    TableService,
)

logger = logging.getLogger(__name__)


class WorkerSignals(QObject):
    started = Signal()
    progress = Signal(int, str)
    result = Signal(object)
    error = Signal(str)
    finished = Signal()


class BaseWorker(QRunnable):
    """QRunnable base class with safe signal/error handling."""

    def __init__(
        self,
        function: Callable[[], Any],
    ) -> None:
        super().__init__()
        self.function = function
        self.signals = WorkerSignals()
        self.cancel_event = threading.Event()
        self.setAutoDelete(True)

    def cancel(self) -> None:
        self.cancel_event.set()

    @Slot()
    def run(self) -> None:
        self.signals.started.emit()

        try:
            if self.cancel_event.is_set():
                return

            result = self.function()

            if not self.cancel_event.is_set():
                self.signals.result.emit(result)

        except Exception as exc:
            logger.exception("Background worker failed")
            message = (
                f"{type(exc).__name__}: {exc}\n\n"
                f"{traceback.format_exc()}"
            )
            self.signals.error.emit(message)

        finally:
            self.signals.finished.emit()


class RenderWorker(BaseWorker):
    def __init__(
        self,
        pdf_path: str,
        page_number: int,
        dpi: int,
    ) -> None:
        super().__init__(
            lambda: PDFService.render_page(
                pdf_path,
                page_number,
                dpi,
            )
        )


class OCRWorker(BaseWorker):
    def __init__(
        self,
        pdf_path: str,
        page_number: int,
        normalized_rect: tuple[float, float, float, float],
        dpi: int,
        tesseract_path: str,
        language: str,
        preprocess: bool,
    ) -> None:
        def operation() -> str:
            image = PDFService.render_region(
                pdf_path,
                page_number,
                normalized_rect,
                dpi,
            )
            service = OCRService(
                tesseract_path,
                language,
                preprocess,
            )
            return service.recognize(image)

        super().__init__(operation)


class ImageWorker(BaseWorker):
    def __init__(
        self,
        pdf_path: str,
        page_number: int,
        normalized_rect: tuple[float, float, float, float],
        dpi: int,
    ) -> None:
        super().__init__(
            lambda: PDFService.render_region(
                pdf_path,
                page_number,
                normalized_rect,
                dpi,
            )
        )


class TableWorker(BaseWorker):
    def __init__(
        self,
        pdf_path: str,
        page_number: int,
        normalized_rect: tuple[float, float, float, float],
        dpi: int,
        tesseract_path: str,
        language: str,
        preprocess: bool,
    ) -> None:
        def operation() -> tuple[Any, bytes]:
            image = PDFService.render_region(
                pdf_path,
                page_number,
                normalized_rect,
                dpi,
            )

            service = OCRService(
                tesseract_path,
                language,
                preprocess,
            )
            table_service = TableService(service)

            table = table_service.recognize(image)
            return table, image

        super().__init__(operation)


class DocxWorker(BaseWorker):
    def __init__(
        self,
        model: DocumentModel,
        output_path: str,
    ) -> None:
        super().__init__(
            lambda: DocxService.generate(
                model,
                output_path,
            )
        )