"""Graphical user interface for regexvalidate."""

from __future__ import annotations

import json
import os
import re
import sys
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path

import PySide2
from PySide2.QtCore import Qt, QTimer
from PySide2.QtGui import QFont
from PySide2.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSplitter,
    QTextBrowser,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

from pytola.core.themes import ThemeManager

qt_dir = Path(PySide2.__file__).parent
plugin_path = str(qt_dir / "plugins" / "platforms")
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = plugin_path

# History file path
HISTORY_FILE = Path.home() / ".pytola" / "regexvalidate" / ".regex_validator_history.json"
HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
MAX_HISTORY_ITEMS = 20


@dataclass(frozen=True)
class ValidationResult:
    """Result of regex validation."""

    is_valid: bool
    matches: list[str]
    error: str | None = None

    def __str__(self) -> str:
        """Return a string representation of the validation result."""
        if self.is_valid:
            match_count = len(self.matches)
            if match_count == 0:
                return "Valid: No matches found"
            if match_count == 1:
                return "Valid: 1 match found"
            return f"Valid: {match_count} matches found"
        return f"Invalid: {self.error}"


class PatternValidationError(Exception):
    """Exception raised for invalid regex patterns."""


class RegexValidator:
    """A class for validating text against regex patterns."""

    def __init__(self, pattern: str, flags: int = 0) -> None:
        """Initialize the validator with a regex pattern.

        Args:
            pattern: The regex pattern to compile
            flags: Regex compilation flags (re.IGNORECASE, re.MULTILINE, etc.)

        Raises
        ------
            PatternValidationError: If the pattern is invalid
        """
        self.pattern = pattern
        self.flags = flags
        try:
            self._compiled_pattern = re.compile(pattern, flags)
        except re.error as e:
            msg = f"Invalid regex pattern '{pattern}': {e}"
            raise PatternValidationError(
                msg,
            ) from e

    def validate(self, text: str) -> ValidationResult:
        """Validate text against the compiled pattern.

        Args:
            text: The text to validate

        Returns
        -------
            ValidationResult containing validation results

        Raises
        ------
            TypeError: If text is None
        """
        if text is None:
            msg = "Text cannot be None"
            raise TypeError(msg)

        try:
            # For validation, we want the full match strings, not tuples from groups
            matches_iter = self._compiled_pattern.finditer(text)
            matches = [match.group(0) for match in matches_iter]
            is_valid = bool(matches) or bool(self._compiled_pattern.search(text))

            if is_valid:
                return ValidationResult(is_valid=True, matches=matches, error=None)
            return ValidationResult(
                is_valid=False,
                matches=[],
                error=f"Text does not match pattern '{self.pattern}'",
            )
        except re.error as e:
            return ValidationResult(
                is_valid=False,
                matches=[],
                error=f"Regex error during validation: {e}",
            )

    def is_valid(self, text: str) -> bool:
        """Check if text is valid.

        Args:
            text: The text to validate

        Returns
        -------
            True if text matches pattern, False otherwise
        """
        result = self.validate(text)
        return result.is_valid

    def find_all_matches(self, text: str) -> list[str]:
        """Find all matches in the text.

        Args:
            text: The text to search

        Returns
        -------
            List of all matches (full match strings, not group tuples)
        """
        if text is None:
            msg = "Text cannot be None"
            raise TypeError(msg)

        # Return full match strings, not tuples from groups
        matches_iter = self._compiled_pattern.finditer(text)
        return [match.group(0) for match in matches_iter]

    @staticmethod
    def escape_pattern(literal_text: str) -> str:
        """Escape special regex characters in literal text.

        Args:
            literal_text: Text to escape

        Returns
        -------
            Escaped regex pattern that matches the literal text
        """
        return re.escape(literal_text)

    def get_pattern_info(self) -> dict:
        """Get information about the compiled pattern.

        Returns
        -------
            Dictionary containing pattern information
        """
        return {
            "pattern": self.pattern,
            "flags": self.flags,
            "compiled_pattern": self._compiled_pattern,
        }

    def __eq__(self, other) -> bool:
        """Check equality with another RegexValidator."""
        if not isinstance(other, RegexValidator):
            return False
        return self.pattern == other.pattern and self.flags == other.flags

    def __hash__(self) -> int:
        """Hash function for use in dictionaries and sets."""
        return hash((self.pattern, self.flags))


class RegexValidatorGUI(QMainWindow):
    """A graphical user interface for validating text against regex patterns."""

    def __init__(self) -> None:
        super().__init__()
        self.setWindowTitle("Regex Validator Tool")
        self.setGeometry(100, 100, 800, 600)

        ThemeManager.apply_theme_safely(self, "high_contrast")

        # Load history
        self.history = self.load_history()
        self.history_items = []  # Current list of history display items

        # Timer for delayed history update to avoid focus loss
        self.history_timer = QTimer()
        self.history_timer.setSingleShot(True)
        self.history_timer.setInterval(1000)  # Wait 1 second after typing stops
        self.history_timer.timeout.connect(self.add_to_history)

        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # Main layout
        main_layout = QVBoxLayout(central_widget)

        # Title and help button
        title_layout = QHBoxLayout()
        title_label = QLabel("Regex Validator Tool")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("font-size: 18px; font-weight: bold; margin: 10px;")
        title_layout.addWidget(title_label)

        help_button = QPushButton("Help")
        help_button.setMaximumWidth(80)
        help_button.clicked.connect(self.show_help)
        title_layout.addWidget(help_button)
        main_layout.addLayout(title_layout)

        # Create splitter for flexible layout
        splitter = QSplitter(Qt.Vertical)
        main_layout.addWidget(splitter)

        # Top group: Input settings
        top_group = QGroupBox("Input Settings")
        top_layout = QVBoxLayout(top_group)

        # Regular expression input
        regex_layout = QHBoxLayout()
        regex_label = QLabel("Regular Expression:")
        self.regex_input = QLineEdit()
        self.regex_input.setPlaceholderText("Enter your regular expression...")
        regex_layout.addWidget(regex_label)
        regex_layout.addWidget(self.regex_input)
        top_layout.addLayout(regex_layout)

        # History selector
        history_layout = QHBoxLayout()
        history_label = QLabel("History:")
        self.history_combo = QComboBox()
        self.history_combo.setMinimumWidth(400)
        self.update_history_combo()
        self.history_combo.currentIndexChanged.connect(self.on_history_selected)
        history_layout.addWidget(history_label)
        history_layout.addWidget(self.history_combo)
        top_layout.addLayout(history_layout)

        # Raw mode checkbox
        self.raw_mode_checkbox = QCheckBox(
            "Raw Mode (Disable Escape Sequence Processing)",
        )
        self.raw_mode_checkbox.setChecked(True)
        top_layout.addWidget(self.raw_mode_checkbox)

        # Test string input
        test_string_label = QLabel("Test String:")
        top_layout.addWidget(test_string_label)

        self.test_string_input = QTextEdit()
        self.test_string_input.setMaximumHeight(100)
        self.test_string_input.setPlaceholderText(
            "Enter the string to test against the regular expression...",
        )
        top_layout.addWidget(self.test_string_input)

        splitter.addWidget(top_group)

        # Bottom group: Results and options
        bottom_group = QGroupBox("Results & Options")
        bottom_layout = QVBoxLayout(bottom_group)

        # Options
        options_layout = QHBoxLayout()

        self.case_insensitive_checkbox = QCheckBox("Case Insensitive (re.IGNORECASE)")
        self.multiline_checkbox = QCheckBox("Multiline (re.MULTILINE)")
        self.dotall_checkbox = QCheckBox("Dotall (re.DOTALL)")

        options_layout.addWidget(self.case_insensitive_checkbox)
        options_layout.addWidget(self.multiline_checkbox)
        options_layout.addWidget(self.dotall_checkbox)
        bottom_layout.addLayout(options_layout)

        # Results label with status indicator
        self.results_label = QLabel("Validation Results: ")
        self.results_label.setStyleSheet("font-size: 14px; font-weight: bold;")
        bottom_layout.addWidget(self.results_label)

        self.results_output = QTextEdit()
        self.results_output.setReadOnly(True)
        bottom_layout.addWidget(self.results_output)

        splitter.addWidget(bottom_group)

        # Set initial sizes for splitter
        splitter.setSizes([200, 400])

        # Connect text changes to auto-validate function
        self.regex_input.textChanged.connect(self.on_text_changed)
        self.test_string_input.textChanged.connect(self.on_text_changed)

    def on_text_changed(self) -> None:
        """Handle text changes and auto-validate."""
        self.validate_regex()
        # Restart the timer to delay history update
        self.history_timer.stop()
        self.history_timer.start()

    def load_history(self) -> list:
        """Load history from file."""
        if HISTORY_FILE.exists():
            try:
                with Path(HISTORY_FILE).open(encoding="utf-8") as f:
                    return json.load(f)
            except Exception:
                pass
        return []

    def save_history(self) -> None:
        """Save history to file."""
        try:
            with Path(HISTORY_FILE).open("w", encoding="utf-8") as f:
                json.dump(self.history, f, indent=2, ensure_ascii=False)
        except Exception:
            pass

    def update_history_combo(self) -> None:
        """Update the history combo box with current history."""
        # Store current selection
        current_index = self.history_combo.currentIndex()
        self.history_combo.itemData(current_index) if current_index > 0 else None

        # Block signals to prevent triggering selection changes
        self.history_combo.blockSignals(True)

        self.history_combo.clear()
        self.history_combo.addItem("-- Select History --")

        # Add history items with display format
        for i, item in enumerate(self.history):
            display_text = f"{i + 1}. {item['pattern'][:50]}{'...' if len(item['pattern']) > 50 else ''}"
            self.history_combo.addItem(display_text, i)

        # Restore selection to first item (placeholder) to avoid focus stealing
        self.history_combo.setCurrentIndex(0)

        # Unblock signals
        self.history_combo.blockSignals(False)

    def on_history_selected(self, index: int) -> None:
        """Handle history item selection."""
        if index <= 0:  # First item is placeholder
            return

        data_index = self.history_combo.itemData(index)
        if data_index is not None and 0 <= data_index < len(self.history):
            item = self.history[data_index]

            # Block signals to prevent auto-validation during restoration
            self.regex_input.blockSignals(True)
            self.test_string_input.blockSignals(True)

            # Restore values
            self.regex_input.setText(item["pattern"])
            self.test_string_input.setPlainText(item["test_string"])
            self.raw_mode_checkbox.setChecked(item.get("raw_mode", True))
            self.case_insensitive_checkbox.setChecked(
                item.get("case_insensitive", False),
            )
            self.multiline_checkbox.setChecked(item.get("multiline", False))
            self.dotall_checkbox.setChecked(item.get("dotall", False))

            # Unblock signals and validate
            self.regex_input.blockSignals(False)
            self.test_string_input.blockSignals(False)
            self.validate_regex()

    def add_to_history(self) -> None:
        """Add current settings to history if they are different."""
        current_pattern = self.regex_input.text()
        current_test_string = self.test_string_input.toPlainText()

        if not current_pattern:
            return

        # Create current item
        current_item = {
            "pattern": current_pattern,
            "test_string": current_test_string,
            "raw_mode": self.raw_mode_checkbox.isChecked(),
            "case_insensitive": self.case_insensitive_checkbox.isChecked(),
            "multiline": self.multiline_checkbox.isChecked(),
            "dotall": self.dotall_checkbox.isChecked(),
            "timestamp": str(datetime.now()) if "datetime" in globals() else None,
        }

        # Check if this item already exists in history
        for item in self.history:
            if (
                item["pattern"] == current_pattern
                and item["test_string"] == current_test_string
                and item.get("raw_mode", True) == current_item["raw_mode"]
            ):
                # Item already exists, move it to front
                self.history.remove(item)
                self.history.insert(0, current_item)
                self.save_history()
                return

        # Add new item to front
        self.history.insert(0, current_item)

        # Limit history size
        if len(self.history) > MAX_HISTORY_ITEMS:
            self.history = self.history[:MAX_HISTORY_ITEMS]

        self.save_history()
        self.update_history_combo()

    def validate_regex(self) -> None:
        """Validate the regex against the test string."""
        regex_pattern = self.regex_input.text()
        test_string = self.test_string_input.toPlainText()

        if not regex_pattern:
            self.results_output.setText("Error: Regular expression is empty.")
            self.results_output.setStyleSheet("background-color: #ffcccc;")
            self.results_label.setText("Validation Results: ✗")
            self.results_label.setStyleSheet(
                "font-size: 14px; font-weight: bold; color: red;",
            )
            return

        if not test_string:
            self.results_output.setText(
                "Warning: Test string is empty, but regex is being tested anyway.",
            )
            self.results_output.setStyleSheet("background-color: #ffffcc;")
            self.results_label.setText("Validation Results: ")
            self.results_label.setStyleSheet(
                "font-size: 14px; font-weight: bold; color: orange;",
            )

        # Build flags based on checkboxes
        flags = 0
        if self.case_insensitive_checkbox.isChecked():
            flags |= re.IGNORECASE
        if self.multiline_checkbox.isChecked():
            flags |= re.MULTILINE
        if self.dotall_checkbox.isChecked():
            flags |= re.DOTALL

        try:
            # Handle raw mode
            if self.raw_mode_checkbox.isChecked():
                # In raw mode, treat the regex as-is without processing escape sequences
                compiled_regex = re.compile(regex_pattern, flags)
            else:
                # In non-raw mode, process escape sequences in the pattern
                compiled_regex = re.compile(regex_pattern, flags)

            # Perform various regex operations
            match_result = compiled_regex.search(test_string)
            findall_result = compiled_regex.findall(test_string)
            finditer_result = list(compiled_regex.finditer(test_string))
            fullmatch_result = compiled_regex.fullmatch(test_string) if test_string else None

            # Prepare results
            results = []
            results.extend(
                (
                    "=" * 50,
                    "REGEX VALIDATION RESULTS",
                    "=" * 50,
                    f"Pattern: {regex_pattern}",
                    f"Test String: {test_string!r}",
                    f"Raw Mode: {'Enabled' if self.raw_mode_checkbox.isChecked() else 'Disabled'}",
                    f"Flags: {flags}",
                    "-" * 50,
                )
            )

            # Match result
            if match_result:
                results.extend(
                    (
                        "MATCH FOUND:",
                        f"  Match: {match_result.group()!r}",
                        f"  Span: {match_result.span()}",
                        f"  Start: {match_result.start()}",
                        f"  End: {match_result.end()}",
                    )
                )

                # Show groups if any
                if match_result.groups():
                    results.append(f"  Groups: {match_result.groups()}")

                if match_result.groupdict():
                    results.append(f"  Named Groups: {match_result.groupdict()}")
            else:
                results.append("NO MATCH FOUND")

            # Full match result
            results.append("\nFULL MATCH:")
            if fullmatch_result:
                results.extend(
                    (
                        f"  Full Match: {fullmatch_result.group()!r}",
                        f"  Full Match Groups: {fullmatch_result.groups()}",
                    )
                )
            else:
                results.append("  No full match")

            # Findall result
            results.append(f"\nFINDALL RESULT ({len(findall_result)} items):")
            if findall_result:
                for i, item in enumerate(findall_result):
                    results.append(f"  [{i}]: {item!r}")
            else:
                results.append("  No matches found")

            # Finditer result
            results.append(f"\nFINDITER RESULT ({len(finditer_result)} matches):")
            if finditer_result:
                for i, match in enumerate(finditer_result):
                    results.append(
                        f"  Match {i + 1}: {match.group()!r} at {match.span()}",
                    )
                    if match.groups():
                        results.append(f"    Groups: {match.groups()}")
            else:
                results.append("  No matches found")

            # Check for special regex features
            results.append("\nREGEX ANALYSIS:")
            if "(" in regex_pattern and ")" in regex_pattern:
                results.append("  Contains capturing groups")
            if "(?:" in regex_pattern:
                results.append("  Contains non-capturing groups")
            if "(?P<" in regex_pattern:
                results.append("  Contains named groups")
            if "?" in regex_pattern:
                results.append("  Contains quantifier modifiers")
            if "\\" in regex_pattern:
                results.append("  Contains escape sequences")

            self.results_output.setText("\n".join(results))

            # Set background color based on match result
            if match_result:
                self.results_output.setStyleSheet("background-color: #ccffcc;")
                self.results_label.setText("Validation Results: ✓")
                self.results_label.setStyleSheet(
                    "font-size: 14px; font-weight: bold; color: green;",
                )
            else:
                self.results_output.setStyleSheet("background-color: #ffcccc;")
                self.results_label.setText("Validation Results: ✗")
                self.results_label.setStyleSheet(
                    "font-size: 14px; font-weight: bold; color: red;",
                )

        except re.error as e:
            error_msg = f"Regex Error: {e!s}"
            self.results_output.setText(error_msg)
            self.results_output.setStyleSheet("background-color: #ffcccc;")
            self.results_label.setText("Validation Results: ✗")
            self.results_label.setStyleSheet(
                "font-size: 14px; font-weight: bold; color: red;",
            )
        except Exception as e:
            error_msg = f"Unexpected Error: {e!s}"
            self.results_output.setText(error_msg)
            self.results_output.setStyleSheet("background-color: #ffcccc;")
            self.results_label.setText("Validation Results: ✗")
            self.results_label.setStyleSheet(
                "font-size: 14px; font-weight: bold; color: red;",
            )

    def show_help(self) -> None:
        """Show regex help dialog."""
        dialog = RegexHelpDialog(self)
        dialog.exec_()


class RegexHelpDialog(QDialog):
    """Dialog showing regex syntax help from HTML file."""

    def __init__(self, parent: QWidget | None = None) -> None:
        super().__init__(parent)
        self.setWindowTitle("Regex Syntax Help")
        self.setMinimumSize(700, 500)
        self.resize(750, 600)

        layout = QVBoxLayout(self)

        # Load HTML content from file
        help_html_path = Path(__file__).parent / "regex_help.html"

        if help_html_path.exists():
            html_content = Path(help_html_path).read_text(encoding="utf-8")
        else:
            html_content = """
            <html><body>
                <h2>Help File Not Found</h2>
                <p>Cannot find help file: <code>regex_help.html</code></p>
                <p>Please ensure the file is located in the same directory as <code>gui.py</code>.</p>
            </body></html>
            """

        # Create text browser to display HTML
        text_browser = QTextBrowser()
        text_browser.setHtml(html_content)
        text_browser.setOpenExternalLinks(False)  # Prevent opening external links

        layout.addWidget(text_browser)

        # Close button
        close_button = QPushButton("Close")
        close_button.setMaximumWidth(100)
        close_button.clicked.connect(self.accept)
        button_layout = QHBoxLayout()
        button_layout.addStretch()
        button_layout.addWidget(close_button)
        layout.addLayout(button_layout)


def main() -> None:
    """Run entry point for the GUI application.

    Gui utility tool
    """
    app = QApplication(sys.argv)

    # Set application font
    font = QFont("Arial", 9)
    app.setFont(font)

    window = RegexValidatorGUI()
    window.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
