"""PySide2 GUI version of docscan application."""

from __future__ import annotations

import json
import logging
import sys
from datetime import datetime
from pathlib import Path
from typing import Any

from PySide2.QtCore import QThread, Signal
from PySide2.QtWidgets import (
    QApplication,
    QCheckBox,
    QFileDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QSpinBox,
    QTableWidget,
    QTableWidgetItem,
    QTabWidget,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

# Import from docscan module
try:
    from docscan import DocumentScanner, Rule
except ImportError:
    from sfi.docscan.docscan import DocumentScanner, Rule

logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)


class ScanWorker(QThread):
    """Worker thread for running document scan in background."""

    progress = Signal(str)
    finished = Signal(dict)
    error = Signal(str)
    progress_update = Signal(int, int)  # current, total

    def __init__(self, scanner: DocumentScanner, threads: int):
        """Initialize worker thread.

        Args:
            scanner: DocumentScanner instance
            threads: Number of worker threads
        """
        super().__init__()
        self.scanner = scanner
        self.threads = threads

    def run(self):
        """Run the document scan."""
        try:
            self.progress.emit("Starting scan...")  # type: ignore

            # Set up custom logger to capture messages
            class ProgressHandler(logging.Handler):
                def __init__(self, signal: Signal):
                    super().__init__()
                    self.signal = signal

                def emit(self, record: logging.LogRecord) -> None:
                    self.signal.emit(lambda: self.format(record))  # type: ignore

            progress_handler = ProgressHandler(self.progress)
            progress_handler.setFormatter(logging.Formatter("%(message)s"))
            logger.addHandler(progress_handler)

            # Set progress callback
            def progress_callback(current: int, total: int) -> None:
                self.progress_update.emit(current, total)  # pyright: ignore[reportAttributeAccessIssue]

            self.scanner.set_progress_callback(progress_callback)

            # Run scan
            results = self.scanner.scan(threads=self.threads, show_progress=True)

            logger.removeHandler(progress_handler)
            self.progress.emit("Scan complete!")  # pyright: ignore[reportAttributeAccessIssue]
            self.finished.emit(results)  # pyright: ignore[reportAttributeAccessIssue]
        except Exception as e:
            self.error.emit(str(e))  # pyright: ignore[reportAttributeAccessIssue]


class DocScanGUI(QWidget):
    """Main GUI window for document scanner application."""

    def __init__(self):
        """Initialize GUI components."""
        super().__init__()
        self.scan_results = None
        self.scan_worker = None
        self.is_scanning = False
        self.init_ui()

    def init_ui(self):
        """Initialize user interface."""
        self.setWindowTitle("Document Scanner GUI")
        self.setMinimumSize(1000, 700)

        # Main layout
        main_layout = QVBoxLayout()
        self.setLayout(main_layout)

        # Create tab widget for options
        tab_widget = QTabWidget()
        self._create_input_tab(tab_widget)
        self._create_options_tab(tab_widget)
        main_layout.addWidget(tab_widget)

        # Create other sections
        self._create_actions_section(main_layout)
        self._create_results_section(main_layout)

    def _create_input_tab(self, tab_widget: QTabWidget) -> None:
        """Create input configuration tab.

        Args:
            tab_widget: Tab widget to add tab to
        """
        input_tab = QWidget()
        input_layout = QVBoxLayout()
        input_tab.setLayout(input_layout)

        # Input directory
        dir_layout = QHBoxLayout()
        dir_label = QLabel("Input Directory:")
        self.dir_edit = QLineEdit(str(Path.cwd()))
        dir_browse_btn = QPushButton("Browse...")
        dir_browse_btn.clicked.connect(self._browse_directory)  # pyright: ignore[reportAttributeAccessIssue]
        self.dir_edit.textChanged.connect(self._on_directory_changed)  # pyright: ignore[reportAttributeAccessIssue]
        dir_layout.addWidget(dir_label)
        dir_layout.addWidget(self.dir_edit)
        dir_layout.addWidget(dir_browse_btn)
        input_layout.addLayout(dir_layout)

        # Rules file
        rules_layout = QHBoxLayout()
        rules_label = QLabel("Rules File:")
        self.rules_edit = QLineEdit("rules.json")
        rules_browse_btn = QPushButton("Browse...")
        rules_browse_btn.clicked.connect(self._browse_rules_file)  # pyright: ignore[reportAttributeAccessIssue]
        rules_layout.addWidget(rules_label)
        rules_layout.addWidget(self.rules_edit)
        rules_layout.addWidget(rules_browse_btn)
        input_layout.addLayout(rules_layout)

        # File types
        types_layout = QVBoxLayout()
        types_label = QLabel("File Types:")
        self.types_edit = QLineEdit("pdf,docx,xlsx,pptx,txt,odt,rtf,epub,csv,xml,html,md")
        types_layout.addWidget(types_label)
        types_layout.addWidget(self.types_edit)
        input_layout.addLayout(types_layout)

        input_layout.addStretch()

        tab_widget.addTab(input_tab, "Input Configuration")

    def _create_options_tab(self, tab_widget: QTabWidget) -> None:
        """Create scan options tab.

        Args:
            tab_widget: Tab widget to add tab to
        """
        options_tab = QWidget()
        options_layout = QVBoxLayout()
        options_tab.setLayout(options_layout)

        # Checkboxes
        self.ocr_checkbox = QCheckBox("Use PDF OCR")
        self.process_pool_checkbox = QCheckBox("Use Process Pool (CPU-intensive)")
        options_layout.addWidget(self.ocr_checkbox)
        options_layout.addWidget(self.process_pool_checkbox)

        # Thread count
        thread_layout = QHBoxLayout()
        thread_label = QLabel("Threads:")
        self.thread_spin = QSpinBox()
        self.thread_spin.setMinimum(1)
        self.thread_spin.setMaximum(16)
        self.thread_spin.setValue(4)
        thread_layout.addWidget(thread_label)
        thread_layout.addWidget(self.thread_spin)
        thread_layout.addStretch()
        options_layout.addLayout(thread_layout)

        # Batch size
        batch_layout = QHBoxLayout()
        batch_label = QLabel("Batch Size:")
        self.batch_spin = QSpinBox()
        self.batch_spin.setMinimum(1)
        self.batch_spin.setMaximum(1000)
        self.batch_spin.setValue(50)
        batch_layout.addWidget(batch_label)
        batch_layout.addWidget(self.batch_spin)
        batch_layout.addStretch()
        options_layout.addLayout(batch_layout)

        options_layout.addStretch()

        tab_widget.addTab(options_tab, "Scan Options")

    def _create_actions_section(self, parent_layout: QVBoxLayout) -> None:
        """Create action buttons section.

        Args:
            parent_layout: Parent layout to add this section to
        """
        actions_layout = QHBoxLayout()

        self.scan_btn = QPushButton("Start Scan")
        self.scan_btn.clicked.connect(self._start_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_btn.setMinimumHeight(40)

        self.pause_btn = QPushButton("Pause")
        self.pause_btn.clicked.connect(self._pause_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.pause_btn.setEnabled(False)
        self.pause_btn.setMinimumHeight(40)

        self.stop_btn = QPushButton("Stop")
        self.stop_btn.clicked.connect(self._stop_scan)  # pyright: ignore[reportAttributeAccessIssue]
        self.stop_btn.setEnabled(False)
        self.stop_btn.setMinimumHeight(40)

        self.save_btn = QPushButton("Save Results")
        self.save_btn.clicked.connect(self._save_results)  # pyright: ignore[reportAttributeAccessIssue]
        self.save_btn.setEnabled(False)
        self.save_btn.setMinimumHeight(40)

        self.clear_btn = QPushButton("Clear Results")
        self.clear_btn.clicked.connect(self._clear_results)  # pyright: ignore[reportAttributeAccessIssue]
        self.clear_btn.setMinimumHeight(40)

        actions_layout.addWidget(self.scan_btn)
        actions_layout.addWidget(self.pause_btn)
        actions_layout.addWidget(self.stop_btn)
        actions_layout.addWidget(self.save_btn)
        actions_layout.addWidget(self.clear_btn)

        parent_layout.addLayout(actions_layout)

    def _create_results_section(self, parent_layout: QVBoxLayout) -> None:
        """Create results display section.

        Args:
            parent_layout: Parent layout to add this section to
        """
        results_group = QGroupBox("Results")
        results_layout = QVBoxLayout()
        results_group.setLayout(results_layout)

        # Summary labels
        summary_layout = QHBoxLayout()
        self.files_label = QLabel("Files Scanned: 0")
        self.matches_label = QLabel("Files with Matches: 0")
        summary_layout.addWidget(self.files_label)
        summary_layout.addWidget(self.matches_label)
        results_layout.addLayout(summary_layout)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        results_layout.addWidget(self.progress_bar)

        # Progress/Log text
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setMaximumHeight(150)
        results_layout.addWidget(QLabel("Progress Log:"))
        results_layout.addWidget(self.log_text)

        # Results table
        self.results_table = QTableWidget()
        self.results_table.setColumnCount(4)
        self.results_table.setHorizontalHeaderLabels(["File", "Type", "Matches", "Time (s)"])
        self.results_table.horizontalHeader().setStretchLastSection(True)
        results_layout.addWidget(QLabel("Match Details:"))
        results_layout.addWidget(self.results_table)

        # Match details text
        self.details_text = QTextEdit()
        self.details_text.setReadOnly(True)
        self.details_text.setMaximumHeight(200)
        results_layout.addWidget(QLabel("Selected Match Context:"))
        results_layout.addWidget(self.details_text)

        # Connect table selection
        self.results_table.itemSelectionChanged.connect(self._show_match_details)  # pyright: ignore[reportAttributeAccessIssue]

        parent_layout.addWidget(results_group)

    def _browse_directory(self) -> None:
        """Open directory browser dialog."""
        dir_path = QFileDialog.getExistingDirectory(self, "Select Input Directory")
        if dir_path:
            self.dir_edit.setText(str(Path(dir_path)))

    def _on_directory_changed(self) -> None:
        """Handle directory text change - auto-search for rules.json."""
        dir_text = self.dir_edit.text()
        if not dir_text:
            return

        try:
            input_dir = Path(dir_text)
            if input_dir.exists() and input_dir.is_dir():
                # Search for rules.json or rules*.json files
                rule_files = list(input_dir.glob("rules.json")) + list(input_dir.glob("rules*.json"))

                if rule_files:
                    # Use the first matching file, prefer exact "rules.json"
                    exact_match = next((f for f in rule_files if f.name == "rules.json"), None)
                    rules_file = exact_match if exact_match else rule_files[0]
                    self.rules_edit.setText(str(rules_file.resolve()))
        except Exception:
            # Ignore errors during directory change handling
            pass

    def _browse_rules_file(self) -> None:
        """Open file browser dialog for rules file."""
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Rules File", "", "JSON Files (*.json)")
        if file_path:
            self.rules_edit.setText(str(Path(file_path)))

    def _load_rules(self) -> list[Rule]:
        """Load rules from JSON file.

        Returns:
            List of Rule objects
        """
        rules_file = Path(self.rules_edit.text())
        if not rules_file.exists():
            # Try finding rules in input directory
            input_dir = Path(self.dir_edit.text())
            rule_files = list(input_dir.glob("rules*.json"))
            if rule_files:
                rules_file = rule_files[0]
                self.rules_edit.setText(str(rules_file.resolve()))
            else:
                raise FileNotFoundError(f"Rules file not found: {rules_file}")

        with open(rules_file, encoding="utf-8") as f:
            rules_data = json.load(f)

        rules = []
        if isinstance(rules_data, list):
            rules = [Rule(rule) for rule in rules_data]
        elif isinstance(rules_data, dict) and "rules" in rules_data:
            rules = [Rule(rule) for rule in rules_data["rules"]]

        return rules

    def _start_scan(self) -> None:
        """Start the document scan."""
        # Validate inputs
        input_dir = Path(self.dir_edit.text())
        if not input_dir.exists() or not input_dir.is_dir():
            QMessageBox.warning(self, "Error", "Invalid input directory")
            return

        try:
            rules = self._load_rules()
            if not rules:
                QMessageBox.warning(self, "Error", "No valid rules found")
                return
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to load rules: {e}")
            return

        # Parse file types
        file_types = [ft.strip() for ft in self.types_edit.text().split(",")]

        # Clear previous results
        self._clear_results()

        # Set scanning state
        self.is_scanning = True

        # Disable scan button during scan, enable pause and stop
        self.scan_btn.setEnabled(False)
        self.pause_btn.setEnabled(True)
        self.stop_btn.setEnabled(True)
        self.pause_btn.setText("Pause")

        # Create scanner
        scanner = DocumentScanner(
            input_dir=input_dir,
            rules=rules,
            file_types=file_types,
            use_pdf_ocr=self.ocr_checkbox.isChecked(),
            use_process_pool=self.process_pool_checkbox.isChecked(),
            batch_size=self.batch_spin.value(),
        )

        # Create and start worker thread
        self.scan_worker = ScanWorker(scanner, self.thread_spin.value())
        self.scan_worker.progress.connect(self._log_message)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_worker.progress_update.connect(self._update_progress)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_worker.finished.connect(self._scan_finished)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_worker.error.connect(self._scan_error)  # pyright: ignore[reportAttributeAccessIssue]
        self.scan_worker.start()

    def _scan_finished(self, results: dict[str, Any]) -> None:
        """Handle scan completion.

        Args:
            results: Scan results dictionary
        """
        self.scan_results = results
        self.is_scanning = False
        self.scan_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        self.save_btn.setEnabled(True)

        # Update summary
        scan_info = results.get("scan_info", {})
        processed = scan_info.get("files_processed", scan_info.get("total_files", 0))
        self.files_label.setText(f"Files Scanned: {processed}/{scan_info.get('total_files', 0)}")
        self.matches_label.setText(f"Files with Matches: {scan_info.get('files_with_matches', 0)}")

        # Update progress bar to 100%
        self.progress_bar.setValue(100)

        # Populate results table
        matches = results.get("matches", [])
        self.results_table.setRowCount(len(matches))

        for row, match_data in enumerate(matches):
            file_path = match_data.get("file_path", "")
            file_type = match_data.get("file_type", "")
            match_count = len(match_data.get("matches", []))
            proc_time = match_data.get("metadata", {}).get("processing_time_seconds", 0)

            self.results_table.setItem(row, 0, QTableWidgetItem(Path(file_path).name))
            self.results_table.setItem(row, 1, QTableWidgetItem(file_type))
            self.results_table.setItem(row, 2, QTableWidgetItem(str(match_count)))
            self.results_table.setItem(row, 3, QTableWidgetItem(f"{proc_time:.3f}"))

        status = "completed" if not results.get("stopped") else "stopped"
        self._log_message(f"Scan {status}. Found matches in {len(matches)} files.")

    def _scan_error(self, error_msg: str) -> None:
        """Handle scan error.

        Args:
            error_msg: Error message
        """
        self.is_scanning = False
        self.scan_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        self._log_message(f"Error: {error_msg}")
        QMessageBox.critical(self, "Error", f"Scan failed: {error_msg}")

    def _pause_scan(self) -> None:
        """Pause or resume the document scan."""
        if self.scan_worker and self.scan_worker.scanner:
            scanner = self.scan_worker.scanner
            if scanner.is_paused():
                # Resume
                scanner.resume()
                self.pause_btn.setText("Pause")
                self._log_message("Scan resumed")
            else:
                # Pause
                scanner.pause()
                self.pause_btn.setText("Resume")
                self._log_message("Scan paused")

    def _stop_scan(self) -> None:
        """Stop the document scan."""
        if not self.is_scanning:
            return

        if self.scan_worker and self.scan_worker.scanner:
            scanner = self.scan_worker.scanner
            scanner.stop()

            # Disable pause and stop buttons immediately
            self.pause_btn.setEnabled(False)
            self.stop_btn.setEnabled(False)

            # Log the stop action
            self._log_message("Stopping scan...")

            # Force UI update
            QApplication.processEvents()

    def _update_progress(self, current: int, total: int) -> None:
        """Update progress bar and file count.

        Args:
            current: Current number of files processed
            total: Total number of files
        """
        if total > 0:
            percentage = int((current / total) * 100)
            self.progress_bar.setValue(percentage)
            self.files_label.setText(f"Files Scanned: {current}/{total}")

    def _show_match_details(self) -> None:
        """Show details of selected match in the results table."""
        selected_rows = self.results_table.selectionModel().selectedRows()
        if not selected_rows or not self.scan_results:
            return

        row = selected_rows[0].row()
        matches = self.scan_results.get("matches", [])

        if row >= len(matches):
            return

        match_data = matches[row]
        details = []

        # File info
        details.append(f"File: {match_data.get('file_path', '')}")
        details.append(f"Type: {match_data.get('file_type', '')}")
        details.append(f"Size: {match_data.get('file_size', 0)} bytes\n")

        # Match info
        for match in match_data.get("matches", []):
            details.append(f"Rule: {match.get('rule_name', '')}")
            details.append(f"Description: {match.get('rule_description', '')}")
            details.append(f"Line {match.get('line_number', 0)}: {match.get('match', '')}")
            details.append("\nContext:")
            for ctx_line in match.get("context", []):
                details.append(f"  {ctx_line}")
            details.append("-" * 50)

        self.details_text.setText("\n".join(details))

    def _save_results(self) -> None:
        """Save scan results to JSON file."""
        if not self.scan_results:
            QMessageBox.warning(self, "Warning", "No results to save")
            return

        default_name = f"scan_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Results", default_name, "JSON Files (*.json)")

        if file_path:
            try:
                with open(file_path, "w", encoding="utf-8") as f:
                    json.dump(self.scan_results, f, indent=2, ensure_ascii=False)
                self._log_message(f"Results saved to: {file_path}")
                QMessageBox.information(self, "Success", f"Results saved to:\n{file_path}")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to save results: {e}")

    def _clear_results(self) -> None:
        """Clear all results and logs."""
        self.scan_results = None
        self.log_text.clear()
        self.results_table.setRowCount(0)
        self.details_text.clear()
        self.files_label.setText("Files Scanned: 0")
        self.matches_label.setText("Files with Matches: 0")
        self.progress_bar.setValue(0)
        self.save_btn.setEnabled(False)

    def _log_message(self, message: str) -> None:
        """Add message to log text area.

        Args:
            message: Message to log
        """
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.append(f"[{timestamp}] {message}")


def main():
    """Main entry point for GUI application."""
    app = QApplication(sys.argv)
    window = DocScanGUI()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
