"""Keyboard cooperative word game using Pygame.

Players take turns selecting adjacent keys to form valid English words.
"""

from dataclasses import dataclass, field
import logging
from pathlib import Path
import secrets
import sys

import pygame

from python_pkg.keyboard_coop._dictionary import load_dictionary

_logger = logging.getLogger(__name__)

# Use cryptographically secure random number generator
_rng = secrets.SystemRandom()

# Initialize Pygame
pygame.init()

# Constants
SCREEN_WIDTH = 1366
SCREEN_HEIGHT = 768
BACKGROUND_COLOR = (30, 30, 40)
KEYBOARD_COLOR = (60, 60, 70)
KEY_COLOR = (80, 80, 90)
KEY_SELECTED_COLOR = (150, 150, 200)
KEY_AVAILABLE_COLOR = (100, 150, 100)
TEXT_COLOR = (255, 255, 255)
PLAYER_COLORS = [(255, 100, 100), (100, 100, 255)]
MIN_WORD_LENGTH = 3

# Keyboard layout
KEYBOARD_LAYOUT = [
    ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
    ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
    ["z", "x", "c", "v", "b", "n", "m"],
]

# Key adjacency mapping
KEY_ADJACENCY = {
    "q": ["w", "a", "s"],
    "w": ["q", "e", "a", "s", "d"],
    "e": ["w", "r", "s", "d", "f"],
    "r": ["e", "t", "d", "f", "g"],
    "t": ["r", "y", "f", "g", "h"],
    "y": ["t", "u", "g", "h", "j"],
    "u": ["y", "i", "h", "j", "k"],
    "i": ["u", "o", "j", "k", "l"],
    "o": ["i", "p", "k", "l"],
    "p": ["o", "l"],
    "a": ["q", "w", "s", "z", "x"],
    "s": ["q", "w", "e", "a", "d", "z", "x", "c"],
    "d": ["w", "e", "r", "s", "f", "x", "c", "v"],
    "f": ["e", "r", "t", "d", "g", "c", "v", "b"],
    "g": ["r", "t", "y", "f", "h", "v", "b", "n"],
    "h": ["t", "y", "u", "g", "j", "b", "n", "m"],
    "j": ["y", "u", "i", "h", "k", "n", "m"],
    "k": ["u", "i", "o", "j", "l", "m"],
    "l": ["i", "o", "p", "k"],
    "z": ["a", "s", "x"],
    "x": ["a", "s", "d", "z", "c"],
    "c": ["s", "d", "f", "x", "v"],
    "v": ["d", "f", "g", "c", "b"],
    "b": ["f", "g", "h", "v", "n"],
    "n": ["g", "h", "j", "b", "m"],
    "m": ["h", "j", "k", "n"],
}


@dataclass
class GameState:
    """State for an ongoing keyboard coop game."""

    current_player: int = 0
    current_word: str = ""
    selected_letters: list[str] = field(default_factory=list)
    score: int = 0
    game_over: bool = False
    message: str = "Player 1: Choose any letter to start!"


@dataclass
class KeyboardState:
    """State for the keyboard layout and adjacency."""

    layout: list[list[str]] = field(default_factory=list)
    available_letters: set[str] = field(default_factory=set)
    adjacency: dict[str, list[str]] = field(default_factory=dict)
    positions: dict[str, pygame.Rect] = field(default_factory=dict)


@dataclass
class FontSet:
    """Collection of fonts used in the game."""

    normal: pygame.font.Font
    large: pygame.font.Font
    small: pygame.font.Font


class KeyboardCoopGame:
    """Main game class for the keyboard cooperative word game."""

    def __init__(self) -> None:
        """Initialize the game window, fonts, and game state."""
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        pygame.display.set_caption("Keyboard Coop Game")
        self.clock = pygame.time.Clock()
        self.fonts = FontSet(
            normal=pygame.font.Font(None, 24),
            large=pygame.font.Font(None, 32),
            small=pygame.font.Font(None, 20),
        )

        # Load dictionary
        self.dictionary = load_dictionary(Path(__file__).parent)

        # Initialize game state
        self.state = GameState()

        # Initialize keyboard state
        self.keyboard = KeyboardState()

        # Generate random keyboard layout and adjacency
        self._generate_random_keyboard()

    def _generate_random_keyboard(self) -> None:
        """Generate a random keyboard layout and calculate adjacencies."""
        # All 26 letters
        all_letters = list("abcdefghijklmnopqrstuvwxyz")
        _rng.shuffle(all_letters)

        # Create random layout with same structure as QWERTY (10-9-7)
        self.keyboard.layout = [
            all_letters[0:10],  # Top row: 10 keys
            all_letters[10:19],  # Middle row: 9 keys
            all_letters[19:26],  # Bottom row: 7 keys
        ]

        # Update available letters
        self.keyboard.available_letters = set(all_letters)

        # Calculate adjacencies based on new layout
        self._calculate_adjacencies()

        # Calculate key positions
        self.keyboard.positions = self._calculate_key_positions()

    def _calculate_adjacencies(self) -> None:
        """Calculate adjacencies based on current keyboard layout."""
        self.keyboard.adjacency = {}

        for row_idx, row in enumerate(self.keyboard.layout):
            for col_idx, letter in enumerate(row):
                adjacents = []

                # Check all 8 directions (including diagonals)
                directions = [
                    (-1, -1),
                    (-1, 0),
                    (-1, 1),  # Above
                    (0, -1),
                    (0, 1),  # Same row
                    (1, -1),
                    (1, 0),
                    (1, 1),  # Below
                ]

                for dr, dc in directions:
                    new_row = row_idx + dr
                    new_col = col_idx + dc

                    # Check bounds
                    if 0 <= new_row < len(self.keyboard.layout) and 0 <= new_col < len(
                        self.keyboard.layout[new_row]
                    ):
                        adjacents.append(self.keyboard.layout[new_row][new_col])

                self.keyboard.adjacency[letter] = adjacents

    def _calculate_key_positions(self) -> dict[str, pygame.Rect]:
        """Calculate the position of each key on screen."""
        positions: dict[str, pygame.Rect] = {}
        key_width = 60
        key_height = 60
        key_spacing = 8
        start_x = 50
        start_y = 320

        for row_idx, row in enumerate(self.keyboard.layout):
            row_offset = row_idx * 30  # Offset for layout
            for col_idx, key in enumerate(row):
                x = start_x + col_idx * (key_width + key_spacing) + row_offset
                y = start_y + row_idx * (key_height + key_spacing)
                positions[key] = pygame.Rect(x, y, key_width, key_height)

        return positions

    def _get_key_at_position(self, pos: tuple[int, int]) -> str | None:
        """Get the key at the given mouse position."""
        for key, rect in self.keyboard.positions.items():
            if rect.collidepoint(pos):
                return key
        return None

    def _is_valid_move(self, letter: str) -> bool:
        """Check if the letter is a valid move."""
        if not self.state.selected_letters:
            return True  # First move can be any letter

        last_letter = self.state.selected_letters[-1]
        return letter in self.keyboard.adjacency[last_letter]

    def _is_valid_word(self, word: str) -> bool:
        """Check if the word is in the dictionary."""
        return word.lower() in self.dictionary

    def _calculate_score(self, word_length: int) -> int:
        """Calculate score exponentially based on word length."""
        if word_length < MIN_WORD_LENGTH:
            return 0
        return 2 ** (word_length - 2)

    def _handle_letter_click(self, letter: str) -> None:
        """Handle clicking on a letter."""
        if letter in self.keyboard.available_letters and self._is_valid_move(letter):
            self.state.selected_letters.append(letter)
            self.state.current_word += letter

            # Update available letters to include adjacent letters AND the same letter
            adjacent_letters = (
                set(self.keyboard.adjacency[letter])
                if letter in self.keyboard.adjacency
                else set()
            )
            adjacent_letters.add(letter)  # Allow selecting the same letter again
            self.keyboard.available_letters = adjacent_letters

            # Switch player
            self.state.current_player = 1 - self.state.current_player
            self.state.message = (
                f"Player {self.state.current_player + 1}: Choose an adjacent letter!"
            )

    def _submit_word(self) -> None:
        """Submit the current word and check if it's valid."""
        if len(self.state.current_word) >= MIN_WORD_LENGTH and self._is_valid_word(
            self.state.current_word
        ):
            points = self._calculate_score(len(self.state.current_word))
            self.state.score += points
            self.state.message = (
                f"'{self.state.current_word}' is valid! +{points} points "
                f"(Total: {self.state.score}) - New keyboard!"
            )

            # Randomize keyboard layout after scoring
            self._generate_random_keyboard()

        elif len(self.state.current_word) < MIN_WORD_LENGTH:
            self.state.message = (
                f"'{self.state.current_word}' is too short! "
                f"(minimum {MIN_WORD_LENGTH} letters)"
            )
        else:
            self.state.message = f"'{self.state.current_word}' is not a valid word!"

        # Reset for next word
        self.state.current_word = ""
        self.state.selected_letters = []
        self.state.current_player = 0

    def _reset_game(self) -> None:
        """Reset the game to initial state."""
        self.state = GameState()

        # Generate new random keyboard layout
        self._generate_random_keyboard()

    def _draw_keyboard(self) -> None:
        """Draw the virtual keyboard."""
        for letter, rect in self.keyboard.positions.items():
            # Determine key color
            if letter in self.state.selected_letters:
                color = KEY_SELECTED_COLOR
            elif letter in self.keyboard.available_letters:
                color = KEY_AVAILABLE_COLOR
            else:
                color = KEY_COLOR

            # Draw key
            pygame.draw.rect(self.screen, color, rect)
            pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)

            # Draw letter
            text = self.fonts.small.render(
                letter.upper(), antialias=True, color=TEXT_COLOR
            )
            text_rect = text.get_rect(center=rect.center)
            self.screen.blit(text, text_rect)

    def _draw_text_line(
        self, text: str, pos: tuple[int, int], font: pygame.font.Font
    ) -> None:
        """Draw a single line of text at the given position."""
        rendered = font.render(text, antialias=True, color=TEXT_COLOR)
        self.screen.blit(rendered, pos)

    def _draw_button(self, rect: pygame.Rect, label: str) -> None:
        """Draw a button with the given label."""
        pygame.draw.rect(self.screen, KEY_COLOR, rect)
        pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
        text = self.fonts.small.render(label, antialias=True, color=TEXT_COLOR)
        self.screen.blit(text, text.get_rect(center=rect.center))

    def _draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]:
        """Draw the user interface."""
        # Title and status
        self._draw_text_line("Keyboard Coop Game", (30, 20), self.fonts.large)
        self._draw_text_line(
            f"Current Word: {self.state.current_word.upper()}",
            (30, 50),
            self.fonts.normal,
        )
        self._draw_text_line(f"Score: {self.state.score}", (30, 75), self.fonts.normal)

        # Current player with color
        player_color = PLAYER_COLORS[self.state.current_player]
        player_text = self.fonts.normal.render(
            f"Current Player: {self.state.current_player + 1}",
            antialias=True,
            color=player_color,
        )
        self.screen.blit(player_text, (30, 100))

        # Message
        self._draw_text_line(self.state.message, (30, 125), self.fonts.normal)

        # Instructions
        instructions = [
            "Instructions:",
            "• Take turns selecting adjacent letters",
            "• Click letters or use keyboard to select",
            "• Same letter can be selected consecutively",
            "• Press ENTER to submit word (min 3 letters)",
            "• Press R to reset game",
            "• Score increases exponentially",
            "• Keyboard randomizes after each valid word!",
        ]
        for idx, instruction in enumerate(instructions):
            self._draw_text_line(instruction, (750, 20 + idx * 22), self.fonts.small)

        # Buttons
        enter_rect = pygame.Rect(750, 190, 120, 40)
        reset_rect = pygame.Rect(880, 190, 120, 40)
        self._draw_button(enter_rect, "ENTER")
        self._draw_button(reset_rect, "RESET")

        return enter_rect, reset_rect

    def _handle_click(self, pos: tuple[int, int]) -> None:
        """Handle mouse clicks."""
        # Check if clicked on a key
        key = self._get_key_at_position(pos)
        if key:
            self._handle_letter_click(key)

        # Check UI buttons
        enter_rect = pygame.Rect(750, 190, 120, 40)
        reset_rect = pygame.Rect(880, 190, 120, 40)

        if enter_rect.collidepoint(pos):
            self._submit_word()
        elif reset_rect.collidepoint(pos):
            self._reset_game()

    def run(self) -> None:
        """Main game loop."""
        running = True

        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:  # Left click
                        self._handle_click(event.pos)
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RETURN:
                        self._submit_word()
                    elif event.key == pygame.K_r:
                        self._reset_game()
                    else:
                        # Handle letter key presses
                        key_name = pygame.key.name(event.key)
                        if len(key_name) == 1 and key_name.isalpha():
                            self._handle_letter_click(key_name.lower())

            # Clear screen
            self.screen.fill(BACKGROUND_COLOR)

            # Draw everything
            self._draw_keyboard()
            self._draw_ui()

            # Update display
            pygame.display.flip()
            self.clock.tick(60)

        pygame.quit()
        sys.exit()


if __name__ == "__main__":
    game = KeyboardCoopGame()
    game.run()
