import customtkinter as ctk
import math
import platform
import subprocess
import time

# --- Настройка современной темы ---
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

# Цветовая палитра (Catppuccin Mocha)
COLORS = {
    "text": "#cdd6f4",
    "muted": "#6c7086",
    "accent": "#89b4fa",
    "accent_dark": "#5d8bd9",
    "start": "#a6e3a1",
    "start_hover": "#8bd585",
    "stop": "#f38ba8",
    "stop_hover": "#eb6f92",
    "stop_btn": "#e5484d",
    "amber": "#f9e2af",
    "btn_bg": "#313244",
    "btn_hover": "#3e3f56",
    "field_bg": "#181825",
    "base": "#181825",
}


def _hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip("#")
    return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))


def _interpolate_color(c1, c2, t):
    """Плавно смешивает два цвета в формате #rrggbb. t от 0 до 1 (0 = c1, 1 = c2)."""
    c1 = _hex_to_rgb(c1)
    c2 = _hex_to_rgb(c2)
    t = max(0.0, min(1.0, t))
    r = round(c1[0] + (c2[0] - c1[0]) * t)
    g = round(c1[1] + (c2[1] - c1[1]) * t)
    b = round(c1[2] + (c2[2] - c1[2]) * t)
    return f"#{r:02x}{g:02x}{b:02x}"


def _danger_color(frac):
    """Цвет «опасной зоны» по доле оставшегося времени.
    frac 1→0: акцент → янтарный → красный (плавно)."""
    frac = max(0.0, min(1.0, frac))
    if frac > 0.5:
        t = (1.0 - frac) / 0.5
        return _interpolate_color(COLORS["accent"], COLORS["amber"], t)
    t = (0.5 - frac) / 0.5
    return _interpolate_color(COLORS["amber"], COLORS["stop"], t)


def _lighten(color, amount):
    """Осветляет цвет в сторону белого (amount 0..1)."""
    return _interpolate_color(color, "#ffffff", amount)


def _darken(color, amount):
    """Затемняет цвет к глубокому тёмному (amount 0..1)."""
    return _interpolate_color(color, "#11111b", amount)


def _button_style(kind):
    """Единый премиум-набор цвета/геометрии для каждого вида кнопки."""
    c = COLORS
    border = _lighten(c["btn_bg"], 0.16)              # мягкая рамка тёмных кнопок
    border_hover = _lighten(c["btn_hover"], 0.40)     # «свечение» при наведении
    muted_fg = _interpolate_color(c["field_bg"], c["btn_bg"], 0.55)
    muted_border = _lighten(c["base"], 0.09)
    if kind == "spin":
        return {
            "fg": c["btn_bg"],
            "hover": _interpolate_color(c["btn_hover"], c["accent"], 0.08),
            "border": border, "hover_border": border_hover,
            "text": c["text"], "text_disabled": c["muted"],
            "disabled_fg": muted_fg, "disabled_border": muted_border,
            "width": 40, "height": 44, "corner_radius": 12,
            "font": ("Segoe UI", 20, "bold"),
        }
    if kind == "preset":
        return {
            "fg": c["btn_bg"],
            "hover": _interpolate_color(c["btn_hover"], c["accent"], 0.10),
            "border": border, "hover_border": border_hover,
            "text": c["muted"], "text_disabled": c["muted"],
            "disabled_fg": muted_fg, "disabled_border": muted_border,
            "width": 50, "height": 34, "corner_radius": 14,
            "font": ("Segoe UI", 12, "bold"),
        }
    if kind == "start":
        return {
            "fg": c["start"], "hover": _lighten(c["start"], 0.25),
            "border": _darken(c["start"], 0.30),     # тёмная «лента» как градиент у краёв
            "hover_border": _lighten(c["start"], 0.35),
            "text": "#11111b", "text_disabled": c["muted"],
            "disabled_fg": muted_fg, "disabled_border": muted_border,
            "width": 150, "height": 52, "corner_radius": 26,
            "font": ("Segoe UI", 16, "bold"),
        }
    if kind == "stop":
        return {
            "fg": c["stop_btn"], "hover": _lighten(c["stop_btn"], 0.25),
            "border": _darken(c["stop_btn"], 0.30),
            "hover_border": _lighten(c["stop_btn"], 0.35),
            "text": "#ffffff", "text_disabled": c["muted"],
            "disabled_fg": muted_fg, "disabled_border": muted_border,
            "width": 150, "height": 52, "corner_radius": 26,
            "font": ("Segoe UI", 16, "bold"),
        }
    raise ValueError(f"неизвестный стиль кнопки: {kind}")


def _style_button(btn, kind, **overrides):
    """Применяет фирменный стиль к кнопке и запоминает его для disabled-режима."""
    style = _button_style(kind)
    style.update(overrides)
    btn._premium_style = style
    btn.configure(
        width=style["width"], height=style["height"],
        corner_radius=style["corner_radius"], border_width=1,
        fg_color=style["fg"], hover_color=style["hover"],
        border_color=style["border"],
        font=style["font"],
        text_color=style["text"], text_color_disabled=style["text_disabled"],
        cursor="hand2")

    if not getattr(btn, "_premium_bound", False):
        def _glow_on(_):
            s = btn._premium_style
            if s and str(btn.cget("state")) != "disabled":
                btn.configure(border_color=s["hover_border"])
        def _glow_off(_):
            s = btn._premium_style
            if s and str(btn.cget("state")) != "disabled":
                btn.configure(border_color=s["border"])
        btn._canvas.bind("<Enter>", _glow_on, add="+")
        btn._canvas.bind("<Leave>", _glow_off, add="+")
        btn._premium_bound = True
    return btn


def _style_field(entry):
    """Полировка поля ввода: мягкая рамка, акцентный курсор и выделение текста."""
    entry.configure(
        border_width=1, corner_radius=14,
        fg_color=COLORS["field_bg"],
        border_color=_lighten(COLORS["field_bg"], 0.16),
        text_color=COLORS["accent"])
    tk_field = getattr(entry, "_entry", None)
    if tk_field is not None:
        tk_field.configure(insertbackground=COLORS["accent"], insertwidth=2,
                           selectbackground=COLORS["accent"],
                           selectforeground="#11111b")
    return entry


def _style_label(label, size=11, weight="normal"):
    """Типографика метки/подписи: приглушённый тон, ровный кегль."""
    label.configure(font=("Segoe UI", size, weight), text_color=COLORS["muted"])
    return label


class StyledButton(ctk.CTkButton):
    """Фирменная кнопка: единый стиль через _style_button; состояние disabled
    автоматически приглушает цвета, а переход в normal возвращает базовые."""

    def __init__(self, *args, style="preset", **kwargs):
        self._premium_style = _button_style(style)
        self._premium_bound = False
        super().__init__(*args, **kwargs)
        _style_button(self, style)
        self.configure(state=str(self.cget("state")))

    def configure(self, require_redraw=False, **kwargs):
        if "state" in kwargs:
            style = getattr(self, "_premium_style", None)
            if style is not None:
                if kwargs["state"] == "disabled":
                    kwargs.setdefault("fg_color", style["disabled_fg"])
                    kwargs.setdefault("hover_color", style["disabled_fg"])
                    kwargs.setdefault("border_color", style["disabled_border"])
                else:
                    kwargs.setdefault("fg_color", style["fg"])
                    kwargs.setdefault("hover_color", style["hover"])
                    kwargs.setdefault("border_color", style["border"])
        super().configure(require_redraw=require_redraw, **kwargs)


class ModernSpinbox(ctk.CTkFrame):
    """Виджет выбора числа с кнопками, плавной анимацией и ручным вводом."""

    def __init__(self, *args, initial_value=0, min_value=0, max_value=59, **kwargs):
        super().__init__(*args, fg_color="transparent", **kwargs)
        self.min_value = min_value
        self.max_value = max_value
        self.value = initial_value
        self._pulse_job = None

        self.grid_columnconfigure((0, 1, 2), weight=1)

        # Кнопка вниз (▼)
        self.minus_btn = StyledButton(
            self, text="−", style="spin", command=self.decrease,
        )
        self.minus_btn.grid(row=0, column=0, padx=(0, 4))

        # Поле ввода числа
        self.entry_var = ctk.StringVar(value=f"{self.value:02d}")
        self.entry = ctk.CTkEntry(
            self, textvariable=self.entry_var, width=66, height=44,
            font=("Consolas", 30, "bold"), justify="center",
        )
        _style_field(self.entry)
        self.entry.grid(row=0, column=1)
        self.entry.configure(validate="key",
                             validatecommand=(self.register(self._validate_digit), "%P"))
        self.entry.bind("<FocusOut>", self._normalize)
        self.entry.bind("<Return>", self._normalize)

        # Кнопка вверх (▲)
        self.plus_btn = StyledButton(
            self, text="+", style="spin", command=self.increase,
        )
        self.plus_btn.grid(row=0, column=2, padx=(4, 0))

    def _validate_digit(self, new_value):
        return new_value == "" or new_value.isdigit()

    def _read_value(self):
        text = self.entry_var.get().strip()
        if text == "":
            return self.value
        try:
            val = int(text)
        except ValueError:
            return self.value
        return max(self.min_value, min(self.max_value, val))

    def _normalize(self, event=None):
        self.value = self._read_value()
        self.entry_var.set(f"{self.value:02d}")

    def _pulse(self, btn):
        """Короткая анимация «нажатия»."""
        btn.configure(width=36)
        if self._pulse_job:
            self.after_cancel(self._pulse_job)
        self._pulse_job = btn.after(60, lambda: btn.configure(width=40))
        self.after(80, self._normalize)

    def increase(self):
        self._pulse(self.plus_btn)
        self.value = self._read_value()
        if self.value < self.max_value:
            self.value += 1
        self.entry_var.set(f"{self.value:02d}")

    def decrease(self):
        self._pulse(self.minus_btn)
        self.value = self._read_value()
        if self.value > self.min_value:
            self.value -= 1
        self.entry_var.set(f"{self.value:02d}")

    def get(self):
        self.value = self._read_value()
        return self.value

    def set_state(self, state):
        self.minus_btn.configure(state=state)
        self.plus_btn.configure(state=state)
        self.entry.configure(state="normal" if state == "normal" else "disabled")


class App(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.title("Таймер выключения")
        self.geometry("420x430")
        self.resizable(False, False)

        self.total_seconds = 0
        self.timer_running = False
        self.remaining_seconds = 0
        self.deadline = 0.0
        self.timer_job = None

        self.configure(fg_color=COLORS["base"])   # статичный фон (без анимации)

        self.create_widgets()
        self.apply_theme()

    # ------------------------------------------------------------- виджеты

    def create_widgets(self):
        content = ctk.CTkFrame(self, fg_color="transparent")
        content.place(relx=0.5, rely=0.5, anchor="center")

        # --- Блок выбора времени ---
        time_frame = ctk.CTkFrame(content, fg_color="transparent")
        time_frame.pack(pady=(6, 6))

        hours_frame = ctk.CTkFrame(time_frame, fg_color="transparent")
        hours_frame.grid(row=0, column=0, padx=18)
        self.hours_label = _style_label(
            ctk.CTkLabel(hours_frame, text="ЧАСЫ"), 11, "bold")
        self.hours_label.pack(pady=(0, 6))
        self.hours_spinbox = ModernSpinbox(hours_frame, initial_value=0,
                                           min_value=0, max_value=99)
        self.hours_spinbox.pack()

        mins_frame = ctk.CTkFrame(time_frame, fg_color="transparent")
        mins_frame.grid(row=0, column=1, padx=18)
        self.mins_label = _style_label(
            ctk.CTkLabel(mins_frame, text="МИНУТЫ"), 11, "bold")
        self.mins_label.pack(pady=(0, 6))
        self.mins_spinbox = ModernSpinbox(mins_frame, initial_value=0,
                                          min_value=0, max_value=59)
        self.mins_spinbox.pack()

        # --- Кнопки быстрого запуска ---
        preset_frame = ctk.CTkFrame(content, fg_color="transparent")
        preset_frame.pack(pady=(0, 6))

        self.preset_buttons = []
        presets = [("30 мин", 30 * 60), ("1 ч", 3600), ("2 ч", 7200),
                   ("3 ч", 10800), ("4 ч", 14400), ("5 ч", 18000)]
        for i, (text, seconds) in enumerate(presets):
            btn = StyledButton(
                preset_frame, text=text, style="preset",
                command=lambda s=seconds: self.start_with_seconds(s))
            btn.grid(row=0, column=i, padx=3)
            self.preset_buttons.append(btn)

        # --- Песочные часы со счётчиком ---
        self.countdown_label = ctk.CTkLabel(
            content, text="00:00:00", font=("Consolas", 38, "bold"),
            text_color=COLORS["accent"], fg_color="transparent")
        self.countdown_label.pack(pady=(0, 8))

        # --- Кнопки управления ---
        btn_frame = ctk.CTkFrame(content, fg_color="transparent")
        btn_frame.pack(pady=(0, 8))

        self.start_btn = StyledButton(
            btn_frame, text="▶  ПУСК", style="start",
            command=self.start_timer)
        self.start_btn.grid(row=0, column=0, padx=12)

        self.stop_btn = StyledButton(
            btn_frame, text="⏹  СТОП", style="stop",
            command=self.stop_timer, state="disabled")
        self.stop_btn.grid(row=0, column=1, padx=12)

        # --- Подпись ---
        self.caption_label = _style_label(
            ctk.CTkLabel(content,
                         text="Выключение компьютера по таймеру"),
            11, "normal")
        self.caption_label.pack(pady=(6, 2))

    # ------------------------------------------------------------- тема

    def apply_theme(self):
        """Перекрашивает все виджеты под палитру COLORS (единый стиль)."""

        for sb in (self.hours_spinbox, self.mins_spinbox):
            _style_button(sb.minus_btn, "spin")
            _style_button(sb.plus_btn, "spin")
            _style_field(sb.entry)
        _style_label(self.hours_label, 11, "bold")
        _style_label(self.mins_label, 11, "bold")
        _style_label(self.caption_label, 11, "normal")

        for btn in self.preset_buttons:
            _style_button(btn, "preset")
            btn.configure(state=str(btn.cget("state")))

        for btn, kind in ((self.start_btn, "start"), (self.stop_btn, "stop")):
            _style_button(btn, kind)
            btn.configure(state=str(btn.cget("state")))

        self.refresh_countdown_color()

    # ----------------------------------------------------- цвет счётчика

    def _current_frac(self):
        """Доля оставшегося времени: 1 (полностью) … 0 (конец)."""
        if not self.timer_running or self.total_seconds <= 0:
            return 1.0
        return max(0.0, min(1.0, self.remaining_seconds / self.total_seconds))

    def refresh_countdown_color(self):
        """Обновляет цвет счётчика по текущему времени."""
        frac = self._current_frac()
        self.countdown_label.configure(text_color=_danger_color(frac))

    # ---------------------------------------------------------- логика

    def start_timer(self):
        if self.timer_running:
            return

        hours = self.hours_spinbox.get()
        minutes = self.mins_spinbox.get()

        if hours == 0 and minutes == 0:
            self._flash_error()
            return

        self.start_with_seconds((hours * 3600) + (minutes * 60))

    def start_with_seconds(self, total_seconds):
        if self.timer_running:
            return

        self.total_seconds = total_seconds
        self.remaining_seconds = total_seconds
        self.deadline = time.monotonic() + total_seconds
        self.timer_running = True

        self.hours_spinbox.set_state("disabled")
        self.mins_spinbox.set_state("disabled")
        self._set_presets_state("disabled")
        self.start_btn.configure(state="disabled")
        self.stop_btn.configure(state="normal")

        self.update_countdown()

    def _set_presets_state(self, state):
        for btn in self.preset_buttons:
            btn.configure(state=state)

    def _flash_error(self):
        self.countdown_label.configure(text="00:00:00", text_color=COLORS["stop"])
        self.stop_btn.master.bell()

        def blink(n):
            if n <= 0:
                self.refresh_countdown_color()
                return
            color = COLORS["stop"] if n % 2 else COLORS["accent"]
            self.countdown_label.configure(text_color=color)
            self.after(200, lambda: blink(n - 1))

        blink(6)

    def update_countdown(self):
        if not self.timer_running:
            return

        remaining_seconds = max(0, math.ceil(self.deadline - time.monotonic()))
        self.remaining_seconds = remaining_seconds

        if remaining_seconds <= 0:
            self.finish_countdown()
            return

        h, remainder = divmod(remaining_seconds, 3600)
        m, s = divmod(remainder, 60)
        text = f"{h:02d}:{m:02d}:{s:02d}"
        frac = remaining_seconds / self.total_seconds if self.total_seconds else 1.0
        self.countdown_label.configure(text=text, text_color=_danger_color(frac))

        ms_left = (self.deadline - time.monotonic()) * 1000
        delay = max(1, min(1000, int(ms_left)))
        self.timer_job = self.after(delay, self.update_countdown)

    def finish_countdown(self):
        self.timer_running = False
        if self.timer_job:
            self.after_cancel(self.timer_job)
            self.timer_job = None
        self.countdown_label.configure(text="ВЫКЛ...", text_color=COLORS["stop"])
        self.execute_shutdown()
    def stop_timer(self):
        self.timer_running = False
        if self.timer_job:
            self.after_cancel(self.timer_job)
            self.timer_job = None

        self.cancel_system_shutdown()

        self.countdown_label.configure(text="00:00:00")
        self.refresh_countdown_color()

        self.hours_spinbox.set_state("normal")
        self.mins_spinbox.set_state("normal")
        self._set_presets_state("normal")
        self.start_btn.configure(state="normal")
        self.stop_btn.configure(state="disabled")

    def execute_shutdown(self):
        self.countdown_label.configure(text="ВЫКЛ...", text_color=COLORS["stop"])
        system = platform.system()
        try:
            if system == "Windows":
                result = subprocess.run(
                    ["shutdown", "/s", "/t", "5", "/c", "Выключение по таймеру"],
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            elif system in ["Linux", "Darwin"]:
                result = subprocess.run(
                    ["shutdown", "-h", "now"],
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            else:
                result = None
        except Exception as exc:
            print(f"Ошибка выключения: {exc}")
            self.countdown_label.configure(text="ОШИБКА ВЫКЛ!", text_color=COLORS["stop"])
            return

        if result is None or result.returncode != 0:
            print(f"Ошибка выключения: код {getattr(result, 'returncode', None)}")
            self.countdown_label.configure(text="ОШИБКА ВЫКЛ!", text_color=COLORS["stop"])
            return
        self.destroy()

    def cancel_system_shutdown(self):
        system = platform.system()
        try:
            if system == "Windows":
                result = subprocess.run(["shutdown", "/a"], stdout=subprocess.DEVNULL,
                                        stderr=subprocess.DEVNULL)
                # 1116: ERROR_NO_SHUTDOWN_IN_PROGRESS
                if result.returncode == 1116:
                    return
            else:
                subprocess.run(["shutdown", "-c"], stdout=subprocess.DEVNULL,
                               stderr=subprocess.DEVNULL)
        except Exception:
            pass


if __name__ == "__main__":
    app = App()
    app.mainloop()
