import sys
from dataclasses import dataclass
from enum import StrEnum, auto
from random import sample
from typing import Any, Callable, Iterator, Self

from PyQt6 import QtGui
from PyQt6.QtCore import QSize, Qt, QTimer
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QLCDNumber,
    QMessageBox,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


@dataclass(slots=True, frozen=True)
class Cords:
    x: int
    y: int


class ButtonType(StrEnum):
    EMPTY = auto()
    BOMB = auto()
    NUMBER = auto()


class Button(QPushButton):
    def __init__(
        self,
        cords: Cords,
        handler1: Callable[[Self, bool], Any],
        handler2: Callable[[Self], Any],
    ) -> None:
        super().__init__()
        self.setFixedSize(50, 50)
        self.type = ButtonType.EMPTY
        self.number = 0
        self.cords = cords
        self.marked: bool = False
        self.revealed: bool = False
        self.left_handler = handler1
        self.right_handler = handler2

    def reveal(self) -> Iterator[Cords]:
        self.revealed = True
        self.setStyleSheet("background-color: grey")
        if self.type is ButtonType.NUMBER:
            self.setText(str(self.number))

    def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
        if e.button() is Qt.MouseButton.LeftButton:
            self.left_handler(self, True)
        elif e.button() is Qt.MouseButton.RightButton:
            self.right_handler(self)

    def get_neighbors(self) -> Iterator[Cords]:
        for x in range(self.cords.x - 1, self.cords.x + 2):
            for y in range(self.cords.y - 1, self.cords.y + 2):
                if x < 0 or x >= 10:
                    continue
                if y < 0 or y >= 10:
                    continue
                if Cords(x, y) == self.cords:
                    continue
                yield Cords(x, y)

    def update_texture(self) -> None:
        if self.marked:
            self.setIcon(QIcon("flag.jpg"))
            self.setIconSize(QSize(50, 50))
        else:
            self.setIcon(QIcon())
            # TODO: remove
            if self.type is ButtonType.BOMB:
                self.setStyleSheet("background-color: red")


class Window(QWidget):
    def __init__(self) -> None:
        super().__init__()
        self.setGeometry(0, 0, 500, 500)
        self.seconds = 0
        self.revealed_buttons: set[Cords] = set()
        self.timer = QTimer()
        self.timer.timeout.connect(self.show_time)
        self.timer.setInterval(1000)
        self.timer.start()
        self.bomb_amount = 10
        self.flags_amount = 0
        self.setWindowTitle("Окно")
        self.create_buttons()
        self.select_bombs()

    def create_buttons(self) -> None:
        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        self.counter = QLabel(f"{self.flags_amount}/{self.bomb_amount}")
        finish_button = QPushButton()
        finish_button.setText("FINISH")
        finish_button.clicked.connect(self.finish)
        self.time = QLCDNumber()
        self.time.setDigitCount(5)
        self.time.display("00:00")
        hbox.addWidget(self.counter)
        hbox.addWidget(finish_button)
        hbox.addWidget(self.time)

        vbox.addLayout(hbox)

        self.buttons: list[list[Button]] = []
        for x in range(10):
            hbox = QHBoxLayout()
            row = []

            for y in range(10):
                button = Button(
                    Cords(x, y),
                    self.button_clicked,
                    self.set_flag,
                )
                row.append(button)
                hbox.addWidget(button)

            self.buttons.append(row)
            vbox.addLayout(hbox)
        self.setLayout(vbox)

    def select_bombs(self) -> None:
        buttons = [j for i in self.buttons for j in i]
        bombs = sample(buttons, self.bomb_amount)
        for bomb in bombs:
            bomb.type = ButtonType.BOMB
            # TODO: remove
            bomb.setStyleSheet("background-color: red")
        for bomb in bombs:
            for cords in bomb.get_neighbors():
                button = self.buttons[cords.x][cords.y]
                if button.type is ButtonType.BOMB:
                    continue
                button.type = ButtonType.NUMBER
                button.number += 1

    def button_clicked(self, button: Button, user: bool) -> None:
        if button.cords in self.revealed_buttons:
            return
        self.revealed_buttons.add(button.cords)
        if button.type is ButtonType.BOMB:
            if user:
                QMessageBox.warning(self, "You lost", "You lost")
                self.close()
            return
        button.reveal()
        if button.type is ButtonType.NUMBER:
            return
        for cords in button.get_neighbors():
            neighbor = self.buttons[cords.x][cords.y]
            self.button_clicked(neighbor, False)

    def set_flag(self, button: Button) -> None:
        if button.revealed:
            return
        was_marked = button.marked
        button.marked = not was_marked
        button.update_texture()
        if was_marked:
            self.flags_amount -= 1
        else:
            self.flags_amount += 1
        self.counter.setText(f"{self.flags_amount}/{self.bomb_amount}")

    def finish(self) -> None:
        buttons = [j for i in self.buttons for j in i]
        bomb = ButtonType.BOMB
        if not all(button.marked for button in buttons if button.type is bomb):
            QMessageBox.warning(self, "You lost", "You lost")
            self.close()
            return
        QMessageBox.information(self, "You won", "You won")
        self.close()

    def show_time(self) -> None:
        self.seconds += 1
        minutes, seconds = divmod(self.seconds, 60)
        self.time.display(f"{minutes:0>2}:{seconds:0>2}")
        if minutes >= 5:
            QMessageBox.warning(self, "You lost", "You lost")
            self.close()
            return


def main() -> None:
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()