#!/usr/bin/env python3
"""Screen locker with workout verification for Arch Linux / i3wm.

Requires user to log their workout to unlock the screen.
"""

from __future__ import annotations

import contextlib
from datetime import datetime, timezone
import json
import logging
from pathlib import Path
import subprocess
import sys
import tkinter as tk
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Callable

_logger = logging.getLogger(__name__)

# Validation limits for workout data
MAX_DISTANCE_KM = 100
MAX_TIME_MINUTES = 600
MAX_PACE_MIN_PER_KM = 20
MIN_EXERCISE_NAME_LEN = 3
MAX_SETS = 20
MAX_REPS = 100
MAX_WEIGHT_KG = 500
SICK_LOCKOUT_SECONDS = 120  # 2 minutes wait when sick
SHUTDOWN_CONFIG_FILE = Path("/etc/shutdown-schedule.conf")
# Helper script path (relative to this file)
ADJUST_SHUTDOWN_SCRIPT = Path(__file__).resolve().parent / "adjust_shutdown_schedule.sh"
# State file to track sick day usage and original config values
SICK_DAY_STATE_FILE = Path(__file__).resolve().parent / "sick_day_state.json"

_STRENGTH_FIELDS: list[tuple[str, int]] = [
    ("Exercises (comma-separated):", 50),
    ("Sets per exercise (comma-separated):", 20),
    ("Reps (comma-sep, + for variable: 12+11+12):", 30),
    ("Weight per exercise kg (comma-separated):", 20),
    ("Total weight lifted (kg):", 15),
]


class ScreenLocker:
    """Screen locker that requires workout logging to unlock."""

    def __init__(self, *, demo_mode: bool = True) -> None:
        """Initialize screen locker with optional demo mode."""
        script_dir = Path(__file__).resolve().parent
        self.log_file = script_dir / "workout_log.json"
        if self.has_logged_today():
            _logger.info("Workout already logged today. Skipping screen lock.")
            sys.exit(0)
        self.root = tk.Tk()
        self.root.title("Workout Locker" + (" [DEMO MODE]" if demo_mode else ""))
        self.demo_mode = demo_mode
        self.lockout_time = 10 if demo_mode else 1800
        self.workout_data: dict[str, str] = {}
        self._setup_window()
        if demo_mode:
            self._setup_demo_close_button()
        self.container = tk.Frame(self.root, bg="#1a1a1a")
        self.container.place(relx=0.5, rely=0.5, anchor="center")
        self.ask_workout_done()
        self._grab_input()

    def _setup_window(self) -> None:
        """Configure the window for fullscreen lock."""
        screen_w = self.root.winfo_screenwidth()
        screen_h = self.root.winfo_screenheight()
        self.root.overrideredirect(True)
        self.root.geometry(f"{screen_w}x{screen_h}+0+0")
        self.root.attributes("-fullscreen", True)
        self.root.attributes("-topmost", True)
        self.root.configure(bg="#1a1a1a", cursor="arrow")

    def _setup_demo_close_button(self) -> None:
        """Add close button for demo mode."""
        close_btn = tk.Button(
            self.root,
            text="✕ Close Demo",
            font=("Arial", 12),
            bg="#ff4444",
            fg="white",
            command=self.close,
            cursor="hand2",
        )
        close_btn.place(x=10, y=10)

    def _grab_input(self) -> None:
        """Force input focus to the locker window."""
        self.root.update_idletasks()
        self.root.focus_force()
        self.root.grab_set_global()

    def clear_container(self) -> None:
        """Remove all widgets from the main container."""
        for widget in self.container.winfo_children():
            widget.destroy()

    # ------------------------------------------------------------------
    # UI helper methods
    # ------------------------------------------------------------------

    def _label(
        self,
        text: str,
        *,
        font_size: int = 36,
        color: str = "white",
        pady: int = 20,
    ) -> tk.Label:
        """Create and pack a bold label in the container."""
        label = tk.Label(
            self.container,
            text=text,
            font=("Arial", font_size, "bold"),
            fg=color,
            bg="#1a1a1a",
        )
        label.pack(pady=pady)
        return label

    def _text(
        self,
        text: str,
        *,
        font_size: int = 18,
        color: str = "white",
        pady: int = 10,
    ) -> tk.Label:
        """Create and pack a non-bold text label in the container."""
        label = tk.Label(
            self.container,
            text=text,
            font=("Arial", font_size),
            fg=color,
            bg="#1a1a1a",
        )
        label.pack(pady=pady)
        return label

    def _button(
        self,
        parent: tk.Widget,
        text: str,
        *,
        bg: str,
        command: Callable[[], None],
        width: int = 10,
    ) -> tk.Button:
        """Create a styled button (caller must pack)."""
        return tk.Button(
            parent,
            text=text,
            font=("Arial", 24, "bold"),
            bg=bg,
            fg="white",
            width=width,
            command=command,
            cursor="hand2" if self.demo_mode else "",
        )

    def _button_row(self) -> tk.Frame:
        """Create and pack a horizontal button container."""
        frame = tk.Frame(self.container, bg="#1a1a1a")
        frame.pack(pady=20)
        return frame

    def _entry_row(
        self,
        label_text: str,
        *,
        width: int = 10,
        font_size: int = 20,
    ) -> tk.Entry:
        """Create a labeled entry row, returning the Entry widget."""
        frame = tk.Frame(self.container, bg="#1a1a1a")
        frame.pack(pady=10)
        tk.Label(
            frame,
            text=label_text,
            font=("Arial", font_size),
            fg="white",
            bg="#1a1a1a",
        ).pack(side="left", padx=10)
        entry = tk.Entry(frame, font=("Arial", font_size), width=width)
        entry.pack(side="left", padx=10)
        return entry

    def _disabled_submit_button(self) -> tk.Button:
        """Create a disabled submit button."""
        btn = tk.Button(
            self.container,
            text="SUBMIT (locked)",
            font=("Arial", 24, "bold"),
            bg="#666666",
            fg="white",
            width=15,
            state="disabled",
            cursor="hand2" if self.demo_mode else "",
        )
        btn.pack(pady=10)
        return btn

    def _back_button(self, command: Callable[[], None]) -> tk.Button:
        """Create and pack a back button."""
        btn = tk.Button(
            self.container,
            text="← BACK",
            font=("Arial", 18),
            bg="#666666",
            fg="white",
            width=15,
            command=command,
            cursor="hand2" if self.demo_mode else "",
        )
        btn.pack(pady=10)
        return btn

    def _setup_form_controls(
        self,
        entries: list[tk.Entry],
        verify_command: Callable[[], None],
        back_command: Callable[[], None],
    ) -> None:
        """Set up timer, submit button, and back button for a form."""
        self.timer_label = self._text("", font_size=16, color="#ffaa00")
        self.submit_btn = self._disabled_submit_button()
        self._back_button(back_command)
        self.submit_unlock_time = 30
        self.entries_to_check = entries
        self.submit_command = verify_command
        self.update_submit_timer()

    # ------------------------------------------------------------------
    # Main screen flows
    # ------------------------------------------------------------------

    def ask_workout_done(self) -> None:
        """Display the initial workout question dialog."""
        self.clear_container()
        self._label("Did you work out today?", pady=30)
        frame = self._button_row()
        self._button(
            frame,
            "YES",
            bg="#00aa00",
            command=self.ask_workout_type,
        ).pack(side="left", padx=20)
        self._button(
            frame,
            "NO",
            bg="#aa0000",
            command=self.ask_if_sick,
        ).pack(side="left", padx=20)

    def ask_if_sick(self) -> None:
        """Display sick day question dialog."""
        self.clear_container()
        self._label("Are you sick?", pady=30)
        self._text(
            "If yes, shutdown time will be moved 1.5 hours earlier",
            color="#ffaa00",
        )
        self._sick_question_buttons()

    def _sick_question_buttons(self) -> None:
        """Create the sick day yes/no buttons."""
        frame = self._button_row()
        self._button(
            frame,
            "YES (sick)",
            bg="#cc6600",
            command=self.handle_sick_day,
            width=12,
        ).pack(side="left", padx=20)
        self._button(
            frame,
            "NO",
            bg="#aa0000",
            command=self.lockout,
            width=12,
        ).pack(side="left", padx=20)

    def _get_sick_day_status(self) -> tuple[str, str]:
        """Determine sick day status text and color."""
        if self._sick_mode_used_today():
            return "Shutdown time already adjusted today", "#ffaa00"
        if self._adjust_shutdown_time_earlier():
            return (
                "Shutdown time moved 1.5 hours earlier ✓\n(Will revert tomorrow)"
            ), "#00aa00"
        return "Could not adjust shutdown time (check permissions)", "#ff4444"

    def handle_sick_day(self) -> None:
        """Handle sick day: adjust shutdown time and start 2-minute wait."""
        self.clear_container()
        status_text, status_color = self._get_sick_day_status()
        self._show_sick_day_ui(status_text, status_color)
        self.sick_remaining_time = SICK_LOCKOUT_SECONDS
        self._update_sick_countdown()

    def _show_sick_day_ui(self, status_text: str, status_color: str) -> None:
        """Display sick day UI labels and countdown."""
        self._label("Sick Day Mode", color="#cc6600", pady=20)
        self._text(status_text, color=status_color)
        self._text(
            "Please wait 2 minutes before unlocking...",
            font_size=24,
            pady=20,
        )
        self.sick_countdown_label = self._label(
            str(SICK_LOCKOUT_SECONDS),
            font_size=80,
            pady=30,
        )

    def _update_sick_countdown(self) -> None:
        """Update the sick day countdown timer."""
        if self.sick_remaining_time > 0:
            self.sick_countdown_label.config(text=str(self.sick_remaining_time))
            self.sick_remaining_time -= 1
            self.root.after(1000, self._update_sick_countdown)
        else:
            # Record sick day and unlock
            self.workout_data["type"] = "sick_day"
            self.workout_data["note"] = "Sick day - shutdown moved earlier"
            self.unlock_screen()

    # ------------------------------------------------------------------
    # Shutdown schedule adjustment
    # ------------------------------------------------------------------

    def _apply_earlier_shutdown(self, today: str) -> bool:
        """Read config, save state, and write earlier shutdown hours."""
        config_values = self._read_shutdown_config()
        if config_values is None:
            return False
        mon_wed_hour, thu_sun_hour, morning_end_hour = config_values
        if not self._save_sick_day_state(today, mon_wed_hour, thu_sun_hour):
            _logger.error("Failed to save state - aborting adjustment")
            return False
        new_mon_wed = max(18, mon_wed_hour - 1)
        new_thu_sun = max(18, thu_sun_hour - 1)
        return self._write_shutdown_config(
            new_mon_wed,
            new_thu_sun,
            morning_end_hour,
        )

    def _adjust_shutdown_time_earlier(self) -> bool:
        """Adjust shutdown schedule 1.5 hours earlier (stricter).

        This can only be used once per day. Original values are saved and
        automatically restored when checked the next day.

        Returns True if successful, False otherwise.
        """
        today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
        self._restore_original_config_if_needed()
        if self._sick_mode_used_today():
            _logger.warning("Sick mode already used today")
            return False
        try:
            return self._apply_earlier_shutdown(today)
        except (OSError, ValueError) as e:
            _logger.warning("Failed to adjust shutdown time: %s", e)
            return False

    def _adjust_shutdown_time_later(self) -> bool:
        """Adjust shutdown schedule 2 hours later as workout reward.

        Returns True if successful, False otherwise.
        """
        try:
            config_values = self._read_shutdown_config()
            if config_values is None:
                return False
            mon_wed_hour, thu_sun_hour, morning_end_hour = config_values
            new_mon_wed = min(23, mon_wed_hour + 2)
            new_thu_sun = min(23, thu_sun_hour + 2)
            return self._write_shutdown_config(
                new_mon_wed,
                new_thu_sun,
                morning_end_hour,
                restore=True,
            )
        except (OSError, ValueError) as e:
            _logger.warning("Failed to adjust shutdown time for workout: %s", e)
            return False

    def _sick_mode_used_today(self) -> bool:
        """Check if sick mode was already used today."""
        if not SICK_DAY_STATE_FILE.exists():
            return False

        try:
            with SICK_DAY_STATE_FILE.open() as f:
                state = json.load(f)
            today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
            return state.get("date") == today
        except (OSError, json.JSONDecodeError):
            return False

    def _save_sick_day_state(
        self,
        date: str,
        orig_mon_wed: int,
        orig_thu_sun: int,
    ) -> bool:
        """Save sick day state with original config values.

        Returns True if saved successfully, False otherwise.
        """
        state = {
            "date": date,
            "original_mon_wed_hour": orig_mon_wed,
            "original_thu_sun_hour": orig_thu_sun,
        }
        try:
            with SICK_DAY_STATE_FILE.open("w") as f:
                json.dump(state, f, indent=2)
        except OSError as e:
            _logger.warning("Failed to save sick day state: %s", e)
            return False

        _logger.info("Saved sick day state for %s", date)
        return True

    def _load_sick_day_state(self) -> tuple[str, int, int] | None:
        """Load sick day state file.

        Returns (date, orig_mon_wed_hour, orig_thu_sun_hour) or None.
        """
        with SICK_DAY_STATE_FILE.open() as f:
            state = json.load(f)
        date = state.get("date")
        orig_mw = state.get("original_mon_wed_hour")
        orig_ts = state.get("original_thu_sun_hour")
        if date is None or orig_mw is None or orig_ts is None:
            return None
        return (str(date), int(orig_mw), int(orig_ts))

    def _write_restored_config(
        self,
        orig_mw: int,
        orig_ts: int,
        state_date: str,
    ) -> None:
        """Write restored config values and clean up state file."""
        config_values = self._read_shutdown_config()
        if config_values:
            _, _, morning_end = config_values
            _logger.info(
                "Restoring original shutdown config from %s",
                state_date,
            )
            self._write_shutdown_config(
                orig_mw,
                orig_ts,
                morning_end,
                restore=True,
            )
        SICK_DAY_STATE_FILE.unlink()
        _logger.info("Removed stale sick day state from %s", state_date)

    def _restore_original_config_if_needed(self) -> None:
        """Restore original config if sick day state is from a previous day."""
        if not SICK_DAY_STATE_FILE.exists():
            return
        try:
            loaded = self._load_sick_day_state()
            if loaded is None:
                return
            state_date, orig_mw, orig_ts = loaded
            today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
            if state_date != today:
                self._write_restored_config(orig_mw, orig_ts, state_date)
        except (OSError, json.JSONDecodeError) as e:
            _logger.warning("Error checking sick day state: %s", e)

    def _read_shutdown_config(self) -> tuple[int, int, int] | None:
        """Read shutdown config. Returns (mw_hour, ts_hour, me_hour) or None."""
        if not SHUTDOWN_CONFIG_FILE.exists():
            _logger.warning("Config not found: %s", SHUTDOWN_CONFIG_FILE)
            return None
        parsed: dict[str, int] = {}
        keys = ("MON_WED_HOUR", "THU_SUN_HOUR", "MORNING_END_HOUR")
        with SHUTDOWN_CONFIG_FILE.open() as f:
            for line in f:
                stripped = line.strip()
                for key in keys:
                    if stripped.startswith(f"{key}="):
                        parsed[key] = int(stripped.split("=")[1])
        if len(parsed) < len(keys):
            _logger.warning("Shutdown config missing required values")
            return None
        return (
            parsed["MON_WED_HOUR"],
            parsed["THU_SUN_HOUR"],
            parsed["MORNING_END_HOUR"],
        )

    def _build_shutdown_cmd(
        self,
        mon_wed: int,
        thu_sun: int,
        morning: int,
        *,
        restore: bool,
    ) -> list[str]:
        """Build the shutdown adjustment command."""
        cmd = ["/usr/bin/sudo", str(ADJUST_SHUTDOWN_SCRIPT)]
        if restore:
            cmd.append("--restore")
        cmd.extend([str(mon_wed), str(thu_sun), str(morning)])
        return cmd

    def _write_shutdown_config(
        self,
        mon_wed_hour: int,
        thu_sun_hour: int,
        morning_end_hour: int,
        *,
        restore: bool = False,
    ) -> bool:
        """Write new shutdown config values using helper script.

        Args:
            mon_wed_hour: Shutdown hour for Monday-Wednesday.
            thu_sun_hour: Shutdown hour for Thursday-Sunday.
            morning_end_hour: Morning end hour.
            restore: If True, allows restoring to later times.

        Returns True if successful, False otherwise.
        """
        if not ADJUST_SHUTDOWN_SCRIPT.exists():
            _logger.warning(
                "Script not found: %s",
                ADJUST_SHUTDOWN_SCRIPT,
            )
            return False
        cmd = self._build_shutdown_cmd(
            mon_wed_hour,
            thu_sun_hour,
            morning_end_hour,
            restore=restore,
        )
        return self._run_shutdown_cmd(cmd, mon_wed_hour, thu_sun_hour)

    def _run_shutdown_cmd(
        self,
        cmd: list[str],
        mon_wed_hour: int,
        thu_sun_hour: int,
    ) -> bool:
        """Execute the shutdown adjustment command."""
        try:
            result = subprocess.run(
                cmd,
                check=True,
                capture_output=True,
                text=True,
            )
        except subprocess.SubprocessError as e:
            _logger.warning("Failed to adjust shutdown config: %s", e)
            return False
        _logger.info(
            "Adjusted shutdown: Mon-Wed=%d, Thu-Sun=%d. %s",
            mon_wed_hour,
            thu_sun_hour,
            result.stdout.strip(),
        )
        return True

    # ------------------------------------------------------------------
    # Lockout flow
    # ------------------------------------------------------------------

    def lockout(self) -> None:
        """Display lockout screen with countdown timer."""
        self.clear_container()
        self.lockout_label = self._label(
            f"Go work out!\nLocked for {self.lockout_time} seconds",
            font_size=48,
            color="#ff4444",
            pady=30,
        )
        self.countdown_label = self._label(
            str(self.lockout_time),
            font_size=120,
            pady=30,
        )
        self.remaining_time = self.lockout_time
        self.update_lockout_countdown()

    def update_lockout_countdown(self) -> None:
        """Update the lockout countdown timer display."""
        if self.remaining_time > 0:
            self.countdown_label.config(text=str(self.remaining_time))
            self.remaining_time -= 1
            self.root.after(1000, self.update_lockout_countdown)
        else:
            self.ask_workout_done()

    # ------------------------------------------------------------------
    # Workout type selection
    # ------------------------------------------------------------------

    def ask_workout_type(self) -> None:
        """Display workout type selection dialog."""
        self.clear_container()
        self._label("What type of workout?", pady=30)
        frame = self._button_row()
        self._button(
            frame,
            "STRENGTH",
            bg="#cc6600",
            command=self.ask_strength_details,
            width=12,
        ).pack(side="left", padx=20)

    # ------------------------------------------------------------------
    # Running workout
    # ------------------------------------------------------------------

    def _create_running_entries(self) -> list[tk.Entry]:
        """Create running workout entry fields."""
        self.distance_entry = self._entry_row("Distance (km):")
        self.time_entry = self._entry_row("Time (minutes):")
        self.pace_entry = self._entry_row("Pace (min/km):")
        return [self.distance_entry, self.time_entry, self.pace_entry]

    def ask_running_details(self) -> None:
        """Display running workout input form."""
        self.clear_container()
        self.workout_data["type"] = "running"
        self._label("Running Details", pady=20)
        entries = self._create_running_entries()
        self._setup_form_controls(
            entries,
            self.verify_running_data,
            self.ask_workout_type,
        )

    def _check_running_ranges(
        self,
        distance: float,
        time_mins: float,
        pace: float,
    ) -> str | None:
        """Check if running values are in valid ranges."""
        if distance <= 0 or distance > MAX_DISTANCE_KM:
            return f"Distance seems unrealistic (0-{MAX_DISTANCE_KM} km)"
        if time_mins <= 0 or time_mins > MAX_TIME_MINUTES:
            return f"Time seems unrealistic (0-{MAX_TIME_MINUTES} minutes)"
        if pace <= 0 or pace > MAX_PACE_MIN_PER_KM:
            return f"Pace seems unrealistic (0-{MAX_PACE_MIN_PER_KM} min/km)"
        expected_pace = time_mins / distance
        tolerance = expected_pace * 0.15  # 15% tolerance
        if abs(pace - expected_pace) > tolerance:
            return (
                f"Pace doesn't match! "
                f"Expected ~{expected_pace:.2f} min/km, got {pace:.2f}"
            )
        return None

    def _validate_running_input(self) -> tuple[float, float, float] | None:
        """Parse and validate running input fields."""
        try:
            distance = float(self.distance_entry.get())
            time_mins = float(self.time_entry.get())
            pace = float(self.pace_entry.get())
        except ValueError:
            self.show_error("Please enter valid numbers")
            return None
        error = self._check_running_ranges(distance, time_mins, pace)
        if error:
            self.show_error(error)
            return None
        return distance, time_mins, pace

    def verify_running_data(self) -> None:
        """Validate running workout data and unlock if valid."""
        result = self._validate_running_input()
        if result is None:
            return
        distance, time_mins, pace = result
        self.workout_data["distance_km"] = str(distance)
        self.workout_data["time_minutes"] = str(time_mins)
        self.workout_data["pace_min_per_km"] = str(pace)
        self.unlock_screen()

    # ------------------------------------------------------------------
    # Strength workout
    # ------------------------------------------------------------------

    def _create_strength_entries(self) -> list[tk.Entry]:
        """Create strength training entry fields."""
        entries = [
            self._entry_row(lbl, width=w, font_size=18) for lbl, w in _STRENGTH_FIELDS
        ]
        (
            self.exercises_entry,
            self.sets_entry,
            self.reps_entry,
            self.weights_entry,
            self.total_weight_entry,
        ) = entries
        return entries

    def ask_strength_details(self) -> None:
        """Display strength training input form."""
        self.clear_container()
        self.workout_data["type"] = "strength"
        self._label("Strength Training Details", pady=20)
        entries = self._create_strength_entries()
        self._setup_form_controls(
            entries,
            self.verify_strength_data,
            self.ask_workout_type,
        )

    def _parse_reps(self, reps_raw: list[str]) -> list[list[int]]:
        """Parse reps input - can be single number or variable reps like '12+11+12'."""
        reps: list[list[int]] = []
        for r in reps_raw:
            if "+" in r:
                reps.append([int(x.strip()) for x in r.split("+")])
            else:
                reps.append([int(r)])
        return reps

    def _validate_strength_inputs(
        self,
        exercises: list[str],
        sets: list[int],
        reps: list[list[int]],
        weights: list[float],
    ) -> str | None:
        """Validate strength workout inputs. Returns error message or None if valid."""
        if not (len(exercises) == len(sets) == len(reps) == len(weights)):
            return "Number of exercises, sets, reps, and weights must match"
        if any(len(ex) < MIN_EXERCISE_NAME_LEN for ex in exercises):
            return "Exercise names too short - be specific"
        if any(s < 1 or s > MAX_SETS for s in sets):
            return f"Sets should be between 1-{MAX_SETS}"
        if any(w < 0 or w > MAX_WEIGHT_KG for w in weights):
            return f"Weights should be between 0-{MAX_WEIGHT_KG} kg"
        return self._validate_reps(exercises, sets, reps)

    def _validate_reps(
        self,
        exercises: list[str],
        sets: list[int],
        reps: list[list[int]],
    ) -> str | None:
        """Validate reps data. Returns error message or None if valid."""
        for i, rep_list in enumerate(reps):
            if any(r < 1 or r > MAX_REPS for r in rep_list):
                return f"Reps should be between 1-{MAX_REPS}"
            if len(rep_list) > 1 and len(rep_list) != sets[i]:
                return (
                    f"For {exercises[i]!r}: variable reps count "
                    f"({len(rep_list)}) doesn't match sets ({sets[i]})"
                )
        return None

    def _calculate_expected_total(
        self,
        sets: list[int],
        reps: list[list[int]],
        weights: list[float],
    ) -> float:
        """Calculate expected total weight lifted."""
        expected_total = 0.0
        for i, rep_list in enumerate(reps):
            if len(rep_list) == 1:
                expected_total += sets[i] * rep_list[0] * weights[i]
            else:
                expected_total += sum(rep_list) * weights[i]
        return expected_total

    def _parse_strength_entries(
        self,
    ) -> tuple[list[str], list[int], list[list[int]], list[float], float]:
        """Parse raw strength training input from entry widgets."""
        exercises = [e.strip() for e in self.exercises_entry.get().split(",")]
        sets = [int(s.strip()) for s in self.sets_entry.get().split(",")]
        reps_raw = [r.strip() for r in self.reps_entry.get().split(",")]
        reps = self._parse_reps(reps_raw)
        weights = [float(w.strip()) for w in self.weights_entry.get().split(",")]
        total_weight = float(self.total_weight_entry.get())
        return exercises, sets, reps, weights, total_weight

    def _check_total_weight(
        self,
        sets: list[int],
        reps: list[list[int]],
        weights: list[float],
        total_weight: float,
    ) -> str | None:
        """Verify total weight matches individual exercise calculations."""
        expected = self._calculate_expected_total(sets, reps, weights)
        tolerance = expected * 0.15  # 15% tolerance
        if abs(total_weight - expected) > tolerance:
            return (
                f"Total weight doesn't match! "
                f"Expected ~{expected:.1f} kg, got {total_weight:.1f}"
            )
        return None

    def _store_strength_data(
        self,
        exercises: list[str],
        sets: list[int],
        reps: list[list[int]],
        weights: list[float],
        total_weight: float,
    ) -> None:
        """Store validated strength workout data."""
        self.workout_data["exercises"] = exercises
        self.workout_data["sets"] = [str(s) for s in sets]
        self.workout_data["reps"] = [
            "+".join(str(r) for r in rep_list) for rep_list in reps
        ]
        self.workout_data["weights_kg"] = [str(w) for w in weights]
        self.workout_data["total_weight_kg"] = str(total_weight)

    def verify_strength_data(self) -> None:
        """Validate strength workout data and unlock if valid."""
        try:
            self._verify_strength_data_inner()
        except ValueError:
            self.show_error("Please enter valid data in correct format")

    def _verify_strength_data_inner(self) -> None:
        """Parse, validate, and store strength data."""
        data = self._parse_strength_entries()
        exercises, sets, reps, weights, total_weight = data
        error = self._validate_strength_inputs(exercises, sets, reps, weights)
        if error:
            self.show_error(error)
            return
        total_err = self._check_total_weight(sets, reps, weights, total_weight)
        if total_err:
            self.show_error(total_err)
            return
        self._store_strength_data(exercises, sets, reps, weights, total_weight)
        self.unlock_screen()

    # ------------------------------------------------------------------
    # Submit timer and entry checking
    # ------------------------------------------------------------------

    def _tick_submit_timer(self) -> None:
        """Decrement submit timer and schedule next tick."""
        self.timer_label.config(
            text=f"Submit available in {self.submit_unlock_time} seconds...",
        )
        self.submit_unlock_time -= 1
        self.root.after(1000, self.update_submit_timer)

    def _try_enable_submit(self) -> None:
        """Enable submit button if all entries are filled."""
        all_filled = all(entry.get().strip() for entry in self.entries_to_check)
        if all_filled:
            self.submit_btn.config(
                text="SUBMIT",
                state="normal",
                bg="#00aa00",
                command=self.submit_command,
            )
            self.timer_label.config(text="You can now submit!")
        else:
            self.timer_label.config(text="Fill all fields to enable submit")
            self.root.after(1000, self.check_entries_filled)

    def update_submit_timer(self) -> None:
        """Update countdown timer and check if submit can be enabled."""
        with contextlib.suppress(tk.TclError):
            if self.submit_unlock_time > 0:
                self._tick_submit_timer()
            else:
                self._try_enable_submit()

    def check_entries_filled(self) -> None:
        """Continuously check if entries are filled after timer expires."""
        with contextlib.suppress(tk.TclError):
            self._try_enable_submit()

    # ------------------------------------------------------------------
    # Error, unlock, and logging
    # ------------------------------------------------------------------

    def show_error(self, message: str) -> None:
        """Display error message with retry option."""
        self.clear_container()
        self._label("ERROR", font_size=48, color="#ff4444", pady=20)
        msg_label = tk.Label(
            self.container,
            text=message,
            font=("Arial", 24),
            fg="white",
            bg="#1a1a1a",
            wraplength=800,
        )
        msg_label.pack(pady=20)
        self._button(
            self.container,
            "TRY AGAIN",
            bg="#0066cc",
            command=self.ask_workout_done,
            width=15,
        ).pack(pady=30)

    def _try_adjust_shutdown_for_workout(self) -> bool:
        """Try to adjust shutdown time later for actual workouts."""
        workout_type = self.workout_data.get("type", "")
        if workout_type not in ("running", "strength"):
            return False
        adjusted = self._adjust_shutdown_time_later()
        if adjusted:
            _logger.info("Shutdown time moved 1.5 hours later as workout reward")
        return adjusted

    def unlock_screen(self) -> None:
        """Save workout log and display success message."""
        self.save_workout_log()
        shutdown_adjusted = self._try_adjust_shutdown_for_workout()
        self.clear_container()
        self._label("Great job! 💪", font_size=48, color="#00ff00", pady=30)
        if shutdown_adjusted:
            self._text(
                "Shutdown time +1.5h later! 🎁",
                font_size=24,
                color="#ffaa00",
            )
        self._text("Screen Unlocked!", font_size=36, pady=20)
        self.root.after(1500, self.close)

    def has_logged_today(self) -> bool:
        """Check if workout has been logged today."""
        if not self.log_file.exists():
            return False

        try:
            with self.log_file.open() as f:
                logs = json.load(f)
        except (OSError, json.JSONDecodeError):
            return False
        else:
            today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
            return today in logs

    def _load_existing_logs(self) -> dict:
        """Load existing workout logs from file."""
        if not self.log_file.exists():
            return {}
        try:
            with self.log_file.open() as f:
                return json.load(f)
        except (OSError, json.JSONDecodeError):
            return {}

    def save_workout_log(self) -> None:
        """Save workout data to log file."""
        logs = self._load_existing_logs()
        today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
        logs[today] = {
            "timestamp": datetime.now(tz=timezone.utc).isoformat(),
            "workout_data": self.workout_data,
        }
        try:
            with self.log_file.open("w") as f:
                json.dump(logs, f, indent=2)
        except OSError as e:
            _logger.warning("Could not save workout log: %s", e)

    def close(self) -> None:
        """Close the application and exit."""
        self.root.destroy()
        sys.exit(0)

    def run(self) -> None:
        """Start the Tkinter main event loop."""
        self.root.mainloop()


if __name__ == "__main__":
    # Check for --production flag
    demo_mode = True  # Default to demo mode for safety

    if len(sys.argv) > 1 and sys.argv[1] == "--production":
        demo_mode = False

    locker = ScreenLocker(demo_mode=demo_mode)
    locker.run()
