"""
Layouts Tab - Group-based window arrangement with drag-and-drop tiles
Supports grid patterns, stacking, and custom positioning
"""

import logging
import re
import subprocess
from typing import Dict, List, Tuple

from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (
    QCheckBox,
    QComboBox,
    QFrame,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QMessageBox,
    QPushButton,
    QScrollArea,
    QSpinBox,
    QVBoxLayout,
    QWidget,
)

from argus_overview.core.layout_manager import GridPattern
from argus_overview.ui.main_tab import get_pattern_positions
from argus_overview.utils.screen import ScreenGeometry, get_screen_geometry


def get_all_patterns():
    """Get all available grid patterns as display strings"""
    return [
        "2x2 Grid",
        "3x1 Row",
        "1x3 Column",
        "4x1 Row",
        "2x3 Grid",
        "3x2 Grid",
        "Main + Sides",
        "Cascade",
        "Stacked (All Same Position)",
        "Custom",
    ]


def pattern_display_to_enum(display_name: str) -> GridPattern:
    """Convert display name to GridPattern enum"""
    mapping = {
        "2x2 Grid": GridPattern.GRID_2X2,
        "3x1 Row": GridPattern.GRID_3X1,
        "1x3 Column": GridPattern.GRID_1X3,
        "4x1 Row": GridPattern.GRID_4X1,
        "Main + Sides": GridPattern.MAIN_PLUS_SIDES,
        "Cascade": GridPattern.CASCADE,
        "Custom": GridPattern.CUSTOM,
    }
    return mapping.get(display_name, GridPattern.CUSTOM)


class DraggableTile(QFrame):
    """Draggable tile representing a character window"""

    tile_moved = Signal(str, int, int)  # char_name, grid_row, grid_col

    def __init__(self, char_name: str, color: QColor, parent=None):
        super().__init__(parent)
        self.char_name = char_name
        self.color = color
        self.grid_row = 0
        self.grid_col = 0
        self.is_stacked = False

        self.setFixedSize(120, 80)
        self.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Raised)
        self.setLineWidth(2)
        self._update_style()

        # Layout
        layout = QVBoxLayout()
        layout.setContentsMargins(5, 5, 5, 5)

        # Character name label
        self.name_label = QLabel(char_name)
        self.name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.name_label.setWordWrap(True)
        self.name_label.setStyleSheet("font-weight: bold; font-size: 10pt;")
        layout.addWidget(self.name_label)

        # Position label
        self.pos_label = QLabel("(0, 0)")
        self.pos_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.pos_label.setStyleSheet("color: #888; font-size: 8pt;")
        layout.addWidget(self.pos_label)

        self.setLayout(layout)

    def _update_style(self):
        """Update tile appearance"""
        bg_color = self.color.name()
        border_color = self.color.darker(150).name()
        self.setStyleSheet(f"""
            QFrame {{
                background-color: {bg_color};
                border: 2px solid {border_color};
                border-radius: 5px;
            }}
        """)

    def set_position(self, row: int, col: int):
        """Set grid position"""
        self.grid_row = row
        self.grid_col = col
        self.pos_label.setText(f"({row}, {col})")

    def set_stacked(self, stacked: bool):
        """Mark tile as stacked"""
        self.is_stacked = stacked
        if stacked:
            self.pos_label.setText("(Stacked)")


class ArrangementGrid(QWidget):
    """Grid for arranging character tiles"""

    arrangement_changed = Signal(dict)  # {char_name: (row, col)}

    def __init__(self, parent=None):
        super().__init__(parent)
        self.logger = logging.getLogger(__name__)
        self.tiles: Dict[str, DraggableTile] = {}
        self.grid_rows = 3
        self.grid_cols = 4

        self._setup_ui()

    def _setup_ui(self):
        """Setup grid UI"""
        self.grid_layout = QGridLayout()
        self.grid_layout.setSpacing(10)
        self.grid_layout.setContentsMargins(10, 10, 10, 10)

        # Create placeholder cells
        for row in range(self.grid_rows):
            for col in range(self.grid_cols):
                cell = QFrame()
                cell.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Sunken)
                cell.setMinimumSize(130, 90)
                cell.setAcceptDrops(True)
                cell.setStyleSheet("""
                    QFrame {
                        background-color: #2a2a2a;
                        border: 1px dashed #555;
                        border-radius: 3px;
                    }
                """)
                self.grid_layout.addWidget(cell, row, col)

        self.setLayout(self.grid_layout)

    def set_grid_size(self, rows: int, cols: int):
        """Resize the grid"""
        self.grid_rows = rows
        self.grid_cols = cols

        # Clear and rebuild
        while self.grid_layout.count():
            item = self.grid_layout.takeAt(0)
            if item.widget():
                item.widget().deleteLater()

        # Recreate cells
        for row in range(rows):
            for col in range(cols):
                cell = QFrame()
                cell.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Sunken)
                cell.setMinimumSize(130, 90)
                cell.setStyleSheet("""
                    QFrame {
                        background-color: #2a2a2a;
                        border: 1px dashed #555;
                        border-radius: 3px;
                    }
                """)
                self.grid_layout.addWidget(cell, row, col)

        # Re-add tiles
        for _char_name, tile in self.tiles.items():
            row = min(tile.grid_row, rows - 1)
            col = min(tile.grid_col, cols - 1)
            tile.set_position(row, col)
            self.grid_layout.addWidget(tile, row, col)

    def clear_tiles(self):
        """Remove all tiles"""
        for tile in list(self.tiles.values()):
            self.grid_layout.removeWidget(tile)
            tile.deleteLater()
        self.tiles.clear()

    def add_character(self, char_name: str, row: int = 0, col: int = 0):
        """Add a character tile to the grid"""
        if char_name in self.tiles:
            return

        # Generate color based on character name hash
        colors = [
            QColor(255, 100, 100, 200),
            QColor(100, 255, 100, 200),
            QColor(100, 100, 255, 200),
            QColor(255, 255, 100, 200),
            QColor(255, 100, 255, 200),
            QColor(100, 255, 255, 200),
            QColor(255, 165, 0, 200),
            QColor(165, 100, 255, 200),
        ]
        color = colors[hash(char_name) % len(colors)]

        tile = DraggableTile(char_name, color)
        tile.set_position(row, col)

        self.tiles[char_name] = tile
        self.grid_layout.addWidget(tile, row, col)

    def get_arrangement(self) -> Dict[str, Tuple[int, int]]:
        """Get current arrangement as dict"""
        return {name: (tile.grid_row, tile.grid_col) for name, tile in self.tiles.items()}

    def auto_arrange_grid(self, pattern: str):
        """Auto-arrange tiles based on pattern"""
        chars = list(self.tiles.keys())
        if not chars:
            return

        # Get positions from shared function
        grid_cols = getattr(self, "grid_cols", 4)
        positions = get_pattern_positions(pattern, len(chars), grid_cols)

        # Handle stacked pattern special case
        if pattern == "Stacked (All Same Position)":
            for tile in self.tiles.values():
                tile.set_stacked(True)

        # Apply positions to tiles
        for idx, char_name in enumerate(chars):
            if idx < len(positions):
                row, col = positions[idx]
                tile = self.tiles[char_name]
                self.grid_layout.removeWidget(tile)
                tile.set_position(row, col)
                self.grid_layout.addWidget(tile, row, col)

        self.arrangement_changed.emit(self.get_arrangement())


class GridApplier:
    """Applies grid patterns to actual windows using xdotool"""

    def __init__(self, layout_manager):
        self.layout_manager = layout_manager
        self.logger = logging.getLogger(__name__)

    def get_screen_geometry(self, monitor: int = 0) -> ScreenGeometry:
        """Get screen geometry for a monitor (delegates to shared utility)"""
        return get_screen_geometry(monitor)

    def apply_arrangement(
        self,
        arrangement: Dict[str, Tuple[int, int]],
        window_map: Dict[str, str],
        screen: ScreenGeometry,
        grid_rows: int,
        grid_cols: int,
        spacing: int = 10,
        stacked: bool = False,
    ) -> bool:
        """
        Apply arrangement to windows

        Args:
            arrangement: {char_name: (row, col)}
            window_map: {char_name: window_id}
            screen: Screen geometry
            grid_rows, grid_cols: Grid dimensions
            spacing: Spacing between windows
            stacked: If True, all windows at same position
        """
        try:
            if stacked:
                # All windows same size and position
                for _char_name, window_id in window_map.items():
                    x = screen.x + spacing
                    y = screen.y + spacing
                    w = screen.width - spacing * 2
                    h = screen.height - spacing * 2

                    self._move_window(window_id, x, y, w, h)
            else:
                # Grid-based arrangement
                cell_width = (screen.width - spacing * (grid_cols + 1)) // grid_cols
                cell_height = (screen.height - spacing * (grid_rows + 1)) // grid_rows

                for char_name, (row, col) in arrangement.items():
                    if char_name not in window_map:
                        continue

                    window_id = window_map[char_name]

                    x = screen.x + spacing + col * (cell_width + spacing)
                    y = screen.y + spacing + row * (cell_height + spacing)

                    self._move_window(window_id, x, y, cell_width, cell_height)

            self.logger.info(f"Applied arrangement to {len(window_map)} windows")
            return True

        except Exception as e:
            self.logger.error(f"Failed to apply arrangement: {e}")
            return False

    def _move_window(self, window_id: str, x: int, y: int, w: int, h: int):
        """Move and resize a single window, with fallback for Wine/Proton windows"""
        import time

        # Validate window ID format (X11: 0x followed by hex digits)
        if not window_id or not re.match(r"^0x[0-9a-fA-F]+$", window_id):
            self.logger.warning(f"Invalid window ID format: {window_id}")
            return

        # Try with --sync first, fallback to no-sync for Wine/Proton windows
        try:
            subprocess.run(
                ["xdotool", "windowmove", "--sync", window_id, str(x), str(y)],
                capture_output=True,
                timeout=2,
            )
        except subprocess.TimeoutExpired:
            # Wine windows don't respond to sync, retry without it
            subprocess.run(
                ["xdotool", "windowmove", window_id, str(x), str(y)], capture_output=True, timeout=2
            )
            time.sleep(0.1)  # Brief pause for window to settle

        try:
            subprocess.run(
                ["xdotool", "windowsize", "--sync", window_id, str(w), str(h)],
                capture_output=True,
                timeout=2,
            )
        except subprocess.TimeoutExpired:
            subprocess.run(
                ["xdotool", "windowsize", window_id, str(w), str(h)], capture_output=True, timeout=2
            )
            time.sleep(0.1)


class LayoutsTab(QWidget):
    """Main Layouts Tab with group-based arrangement"""

    layout_applied = Signal(str)

    def __init__(self, layout_manager, main_tab, settings_manager=None, character_manager=None):
        super().__init__()
        self.layout_manager = layout_manager
        self.main_tab = main_tab
        self.settings_manager = settings_manager
        self.character_manager = character_manager
        self.logger = logging.getLogger(__name__)

        self.grid_applier = GridApplier(layout_manager)
        self.cycling_groups: Dict[str, List[str]] = {}

        self._load_groups()
        self._setup_ui()

    def _load_groups(self):
        """Load cycling groups from settings"""
        if self.settings_manager:
            groups = self.settings_manager.get("cycling_groups", {})
            if isinstance(groups, dict):
                self.cycling_groups = groups

        # Ensure default group exists
        if "Default" not in self.cycling_groups:
            self.cycling_groups["Default"] = []

    def _setup_ui(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(10, 10, 10, 10)

        # Top section: Group selector and pattern
        top_section = self._create_top_section()
        layout.addWidget(top_section)

        # Middle section: Arrangement grid
        grid_section = self._create_grid_section()
        layout.addWidget(grid_section, stretch=1)

        # Bottom section: Apply buttons
        bottom_section = self._create_bottom_section()
        layout.addWidget(bottom_section)

        self.setLayout(layout)

        # Initial update
        self._on_group_selected()

    def _create_top_section(self) -> QWidget:
        """Create group selector and pattern options"""
        section = QGroupBox("Layout Configuration")
        layout = QHBoxLayout()

        # Group selector
        group_layout = QVBoxLayout()
        group_layout.addWidget(QLabel("Select Group:"))

        self.group_combo = QComboBox()
        self._refresh_groups()
        self.group_combo.currentTextChanged.connect(self._on_group_selected)
        group_layout.addWidget(self.group_combo)

        self.refresh_groups_btn = QPushButton("Refresh Groups")
        self.refresh_groups_btn.clicked.connect(self._refresh_groups)
        group_layout.addWidget(self.refresh_groups_btn)

        layout.addLayout(group_layout)

        # Pattern selector
        pattern_layout = QVBoxLayout()
        pattern_layout.addWidget(QLabel("Grid Pattern:"))

        self.pattern_combo = QComboBox()
        self.pattern_combo.addItems(get_all_patterns())
        self.pattern_combo.currentTextChanged.connect(self._on_pattern_changed)
        pattern_layout.addWidget(self.pattern_combo)

        self.auto_arrange_btn = QPushButton("Auto-Arrange")
        self.auto_arrange_btn.clicked.connect(self._auto_arrange)
        pattern_layout.addWidget(self.auto_arrange_btn)

        layout.addLayout(pattern_layout)

        # Grid size options
        size_layout = QVBoxLayout()
        size_layout.addWidget(QLabel("Grid Size:"))

        size_row = QHBoxLayout()
        self.rows_spin = QSpinBox()
        self.rows_spin.setRange(1, 6)
        self.rows_spin.setValue(3)
        self.rows_spin.setPrefix("Rows: ")
        self.rows_spin.valueChanged.connect(self._update_grid_size)
        size_row.addWidget(self.rows_spin)

        self.cols_spin = QSpinBox()
        self.cols_spin.setRange(1, 6)
        self.cols_spin.setValue(4)
        self.cols_spin.setPrefix("Cols: ")
        self.cols_spin.valueChanged.connect(self._update_grid_size)
        size_row.addWidget(self.cols_spin)

        size_layout.addLayout(size_row)

        # Spacing
        spacing_row = QHBoxLayout()
        spacing_row.addWidget(QLabel("Spacing:"))
        self.spacing_spin = QSpinBox()
        self.spacing_spin.setRange(0, 50)
        self.spacing_spin.setValue(10)
        self.spacing_spin.setSuffix(" px")
        spacing_row.addWidget(self.spacing_spin)
        size_layout.addLayout(spacing_row)

        layout.addLayout(size_layout)

        # Monitor selector
        monitor_layout = QVBoxLayout()
        monitor_layout.addWidget(QLabel("Monitor:"))

        self.monitor_spin = QSpinBox()
        self.monitor_spin.setRange(0, 3)
        self.monitor_spin.setValue(0)
        monitor_layout.addWidget(self.monitor_spin)

        # Stacking checkbox
        self.stack_checkbox = QCheckBox("Stack Windows")
        self.stack_checkbox.setToolTip("Place all windows at the same position (overlapping)")
        monitor_layout.addWidget(self.stack_checkbox)

        layout.addLayout(monitor_layout)

        section.setLayout(layout)
        return section

    def _create_grid_section(self) -> QWidget:
        """Create arrangement grid"""
        section = QGroupBox("Window Arrangement")
        layout = QVBoxLayout()

        # Instructions
        instructions = QLabel(
            "Characters from the selected group are shown below. "
            "Click 'Auto-Arrange' to position them based on the selected pattern, "
            "or drag tiles to customize positions."
        )
        instructions.setWordWrap(True)
        instructions.setStyleSheet("color: #888; font-style: italic; padding: 5px;")
        layout.addWidget(instructions)

        # Scroll area for grid
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setMinimumHeight(300)

        self.arrangement_grid = ArrangementGrid()
        scroll.setWidget(self.arrangement_grid)

        layout.addWidget(scroll)

        section.setLayout(layout)
        return section

    def _create_bottom_section(self) -> QWidget:
        """Create apply buttons section"""
        section = QWidget()
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        # Info label
        self.info_label = QLabel("Select a group to begin")
        self.info_label.setStyleSheet("color: #888;")
        layout.addWidget(self.info_label)

        layout.addStretch()

        # Apply to active windows button
        self.apply_active_btn = QPushButton("Apply to Active Windows")
        self.apply_active_btn.setToolTip("Apply layout to currently detected EVE windows")
        self.apply_active_btn.clicked.connect(self._apply_to_active_windows)
        self.apply_active_btn.setStyleSheet("""
            QPushButton {
                background-color: #ff8c00;
                color: black;
                font-weight: bold;
                padding: 10px 20px;
            }
            QPushButton:hover {
                background-color: #ffa500;
            }
        """)
        layout.addWidget(self.apply_active_btn)

        section.setLayout(layout)
        return section

    def _refresh_groups(self):
        """Refresh group list from settings"""
        self._load_groups()

        current = self.group_combo.currentText() if self.group_combo.count() > 0 else None

        self.group_combo.blockSignals(True)
        self.group_combo.clear()

        # Add "All Active Windows" option
        self.group_combo.addItem("All Active Windows")

        for group_name in sorted(self.cycling_groups.keys()):
            self.group_combo.addItem(group_name)

        # Restore selection
        if current:
            idx = self.group_combo.findText(current)
            if idx >= 0:
                self.group_combo.setCurrentIndex(idx)

        self.group_combo.blockSignals(False)

        self.logger.info(f"Loaded {len(self.cycling_groups)} groups")

    def _on_group_selected(self):
        """Handle group selection"""
        group_name = self.group_combo.currentText()

        # Clear current tiles
        self.arrangement_grid.clear_tiles()

        if group_name == "All Active Windows":
            # Get all active windows from main_tab
            if hasattr(self.main_tab, "window_manager"):
                for _window_id, frame in self.main_tab.window_manager.preview_frames.items():
                    self.arrangement_grid.add_character(frame.character_name)

            self.info_label.setText("Showing all active windows")
        else:
            # Get characters from group
            members = self.cycling_groups.get(group_name, [])
            for idx, char_name in enumerate(members):
                row = idx // self.arrangement_grid.grid_cols
                col = idx % self.arrangement_grid.grid_cols
                self.arrangement_grid.add_character(char_name, row, col)

            self.info_label.setText(f"Group '{group_name}': {len(members)} characters")

        # Auto-arrange if pattern is selected
        if self.pattern_combo.currentText() != "Custom":
            self._auto_arrange()

    def _on_pattern_changed(self):
        """Handle pattern change"""
        pattern = self.pattern_combo.currentText()

        # Update stacking checkbox
        if pattern == "Stacked (All Same Position)":
            self.stack_checkbox.setChecked(True)
        else:
            self.stack_checkbox.setChecked(False)

        # Auto-arrange with new pattern
        self._auto_arrange()

    def _update_grid_size(self):
        """Update grid dimensions"""
        rows = self.rows_spin.value()
        cols = self.cols_spin.value()
        self.arrangement_grid.set_grid_size(rows, cols)

    def _auto_arrange(self):
        """Auto-arrange tiles based on pattern"""
        pattern = self.pattern_combo.currentText()
        self.arrangement_grid.auto_arrange_grid(pattern)

    def _apply_to_active_windows(self):
        """Apply layout to active windows"""
        if not hasattr(self.main_tab, "window_manager"):
            QMessageBox.warning(self, "Error", "Main tab not initialized")
            return

        # Get arrangement
        arrangement = self.arrangement_grid.get_arrangement()
        if not arrangement:
            QMessageBox.warning(self, "No Windows", "No windows in arrangement")
            return

        # Build window map (char_name -> window_id)
        window_map = {}
        wm = self.main_tab.window_manager

        for window_id, frame in wm.preview_frames.items():
            if frame.character_name in arrangement:
                window_map[frame.character_name] = window_id

        if not window_map:
            QMessageBox.warning(
                self,
                "No Matching Windows",
                "None of the characters in the arrangement have active windows.\n\n"
                "Make sure the EVE clients are running and detected.",
            )
            return

        # Get screen geometry
        monitor = self.monitor_spin.value()
        screen = self.grid_applier.get_screen_geometry(monitor)

        if not screen:
            QMessageBox.warning(self, "Error", "Could not get screen geometry")
            return

        # Apply arrangement
        success = self.grid_applier.apply_arrangement(
            arrangement=arrangement,
            window_map=window_map,
            screen=screen,
            grid_rows=self.rows_spin.value(),
            grid_cols=self.cols_spin.value(),
            spacing=self.spacing_spin.value(),
            stacked=self.stack_checkbox.isChecked(),
        )

        if success:
            QMessageBox.information(
                self, "Success", f"Applied layout to {len(window_map)} windows!"
            )
            self.layout_applied.emit(self.pattern_combo.currentText())
        else:
            QMessageBox.warning(self, "Error", "Failed to apply layout. Check logs for details.")

    def refresh_groups_from_settings(self):
        """Called when groups change in hotkeys tab"""
        self._refresh_groups()
        self._on_group_selected()
