import configparser
import os
import shutil
import sys
import threading
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple

import tkinter as tk
from tkinter import messagebox, ttk

from steam_required_ids import extract_required_item_ids, resolve_workshop_names


# Minimal dark theme colors
BG = "#16181c"
PANEL_BG = "#1f232a"
FG = "#e6e6e6"
MUTED = "#a0a6ad"
ACCENT = "#4f8cff"


DEFAULT_INI_PATH = str(Path.home() / "AppData" / "LocalLow" / "Triassic Games" / "Sea Power" / "usersettings.ini")


@dataclass
class InstalledMod:
    mod_id: str
    enabled: bool


def read_installed_mods(ini_path: str) -> Dict[str, InstalledMod]:
    parser = configparser.ConfigParser()
    parser.optionxform = str
    if not os.path.exists(ini_path):
        raise FileNotFoundError(f"usersettings.ini not found at: {ini_path}")
    parser.read(ini_path, encoding="utf-8")

    if "[LoadOrder]" in "\n".join(open(ini_path, encoding="utf-8").readlines()):
        section = "LoadOrder"
    else:
        section = "LoadOrder"

    if section not in parser:
        return {}

    mods: Dict[str, InstalledMod] = {}
    for key, value in parser[section].items():
        if not key.lower().startswith("mod"):
            continue
        # Format: ModXDirectory=<id or name>,<True|False>
        parts = value.split(",")
        if not parts:
            continue
        left_token = parts[0].strip()
        # Prefer numeric Workshop ID if present at start, else keep full token
        num_prefix = ""
        for ch in left_token:
            if ch.isdigit():
                num_prefix += ch
            else:
                break
        mod_id = num_prefix if num_prefix else left_token
        enabled = False
        if len(parts) > 1:
            enabled = parts[1].strip().lower() == "true"
        mods[mod_id] = InstalledMod(mod_id=mod_id, enabled=enabled)
    return mods


def enable_mods_in_ini(ini_path: str, ids_to_enable: List[str]) -> Tuple[int, int]:
    parser = configparser.ConfigParser()
    parser.optionxform = str
    parser.read(ini_path, encoding="utf-8")

    section = "LoadOrder"
    if section not in parser:
        raise ValueError("[LoadOrder] section not found in usersettings.ini")

    # Backup before changes
    backup_path = ini_path + ".bak"
    shutil.copyfile(ini_path, backup_path)

    enabled_count = 0
    missing_count = 0

    # Maintain order and preserve original left token (may include name after ID)
    items = list(parser[section].items())
    # Maintain order
    for target_id in ids_to_enable:
        found_key = None
        for key, value in items:
            if not key.lower().startswith("mod"):
                continue
            parts = value.split(",")
            if not parts:
                continue
            left_token = parts[0].strip()
            # Extract numeric prefix for matching
            num_prefix = ""
            for ch in left_token:
                if ch.isdigit():
                    num_prefix += ch
                else:
                    break
            mod_id = num_prefix if num_prefix else left_token
            if mod_id == target_id:
                found_key = key
                current_enabled = False
                if len(parts) > 1:
                    current_enabled = parts[1].strip().lower() == "true"
                if not current_enabled:
                    # Preserve original left token, only toggle enabled flag
                    new_value = f"{left_token},True"
                    parser[section][key] = new_value
                    enabled_count += 1
                break
        if not found_key:
            missing_count += 1

    with open(ini_path, "w", encoding="utf-8") as f:
        parser.write(f)

    return enabled_count, missing_count


class SeaLoaderApp(tk.Tk):
    def __init__(self, ini_path: str):
        super().__init__()
        self.title("SeaLoader")
        self.configure(bg=BG)
        self.geometry("960x560")
        self.minsize(800, 480)

        self.ini_path = ini_path
        self.installed_mods: Dict[str, InstalledMod] = {}
        self.required_ids: List[str] = []
        self.installed_names_map: Dict[str, str] = {}
        self._icon_img = None

        self._style()
        self._layout()
        self._load_installed_mods()

        # App icon from PNG (optional)
        try:
            icon_path = self._resource_path("SeaLoader.png")
            if icon_path.exists():
                self._icon_img = tk.PhotoImage(file=str(icon_path))
                self.iconphoto(True, self._icon_img)
        except Exception:
            pass

        # Help shortcut
        try:
            self.bind("<F1>", lambda e: self._on_help())
        except Exception:
            pass

    def _style(self) -> None:
        style = ttk.Style(self)
        try:
            style.theme_use("clam")
        except Exception:
            pass
        style.configure("TFrame", background=BG)
        style.configure("Panel.TFrame", background=PANEL_BG)
        style.configure("TLabel", background=PANEL_BG, foreground=FG)
        style.configure("Header.TLabel", background=BG, foreground=FG, font=("Segoe UI", 12, "bold"))
        style.configure("TButton", background=PANEL_BG, foreground=FG, borderwidth=0)
        style.map("TButton", background=[("active", ACCENT)])
        style.configure("TEntry", fieldbackground=PANEL_BG, foreground=FG, insertcolor=FG)
        style.configure("TScrollbar", background=PANEL_BG, troughcolor=BG)
        style.configure("Treeview", background=PANEL_BG, fieldbackground=PANEL_BG, foreground=FG, borderwidth=0)
        style.configure("Treeview.Heading", background=PANEL_BG, foreground=MUTED)

    def _layout(self) -> None:
        top = ttk.Frame(self, style="TFrame")
        top.pack(fill=tk.X, padx=12, pady=(12, 6))

        url_label = ttk.Label(top, text="Workshop URL:", style="TLabel")
        url_label.pack(side=tk.LEFT)
        self.url_var = tk.StringVar()
        self.url_entry = ttk.Entry(top, textvariable=self.url_var, width=70)
        self.url_entry.pack(side=tk.LEFT, padx=8, fill=tk.X, expand=True)
        self.fetch_btn = ttk.Button(top, text="Fetch Required Mods", command=self._on_fetch)
        self.fetch_btn.pack(side=tk.LEFT)

        main = ttk.Frame(self, style="TFrame")
        main.pack(fill=tk.BOTH, expand=True, padx=12, pady=6)

        left_panel = ttk.Frame(main, style="Panel.TFrame")
        left_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        right_panel = ttk.Frame(main, style="Panel.TFrame")
        right_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0))

        left_header = ttk.Label(left_panel, text="Installed Mods", style="Header.TLabel")
        left_header.pack(anchor="w", padx=8, pady=8)
        self.installed_tree = self._create_tree(left_panel, ["Mod ID", "Name", "Enabled"], [0.30, 0.55, 0.15])
        self.installed_tree.pack(fill=tk.BOTH, expand=True, padx=8, pady=(0, 8))

        right_header = ttk.Label(right_panel, text="Scenario Required Mods", style="Header.TLabel")
        right_header.pack(anchor="w", padx=8, pady=8)
        self.required_tree = self._create_tree(right_panel, ["Mod ID", "Name", "Status"], [0.25, 0.60, 0.15])
        self.required_tree.pack(fill=tk.BOTH, expand=True, padx=8, pady=(0, 8))

        bottom = ttk.Frame(self, style="TFrame")
        bottom.pack(fill=tk.X, padx=12, pady=(0, 12))
        self.enable_btn = ttk.Button(bottom, text="Enable Matching Mods", command=self._on_enable)
        self.enable_btn.pack(side=tk.LEFT)
        self.help_btn = ttk.Button(bottom, text="Help", command=self._on_help)
        self.help_btn.pack(side=tk.RIGHT)

    def _create_tree(self, parent, columns: List[str], ratios: List[float]):
        tree = ttk.Treeview(parent, columns=columns, show="headings", height=12)
        for col in columns:
            tree.heading(col, text=col)
            tree.column(col, width=50, anchor=tk.W, stretch=True)

        vsb = ttk.Scrollbar(parent, orient="vertical", command=tree.yview)
        hsb = ttk.Scrollbar(parent, orient="horizontal", command=tree.xview)
        tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        vsb.pack(side=tk.LEFT, fill=tk.Y)
        hsb.pack(side=tk.BOTTOM, fill=tk.X)

        self._bind_auto_columns(parent, tree, ratios)
        return tree

    def _bind_auto_columns(self, container, tree: ttk.Treeview, ratios: List[float]) -> None:
        ratios = [r / sum(ratios) for r in ratios]
        def on_resize(event):
            # Reserve some space for vertical scrollbar and paddings
            total = max(event.width - 18, 100)
            for col, ratio in zip(tree["columns"], ratios):
                tree.column(col, width=int(total * ratio))
        container.bind("<Configure>", on_resize, add="+")

    def _load_installed_mods(self) -> None:
        try:
            self.installed_mods = read_installed_mods(self.ini_path)
        except FileNotFoundError as e:
            messagebox.showerror("SeaLoader", str(e))
            self.installed_mods = {}
        self._refresh_installed_list()
        self._resolve_installed_names_async()

    def _refresh_installed_list(self) -> None:
        for row in self.installed_tree.get_children():
            self.installed_tree.delete(row)
        for mod in self.installed_mods.values():
            status = "Yes" if mod.enabled else "No"
            display_name = self.installed_names_map.get(mod.mod_id, "")
            self.installed_tree.insert("", tk.END, values=(mod.mod_id, display_name, status))

    def _resolve_installed_names_async(self) -> None:
        ids = [mid for mid in self.installed_mods.keys() if mid.isdigit()]
        ids_to_lookup = [mid for mid in ids if mid not in self.installed_names_map]
        if not ids_to_lookup:
            return

        def task():
            try:
                names = resolve_workshop_names(ids_to_lookup)
            except Exception:
                names = {}

            def update():
                if names:
                    self.installed_names_map.update(names)
                    self._refresh_installed_list()

            self.after(0, update)

        threading.Thread(target=task, daemon=True).start()

    def _on_fetch(self) -> None:
        url = self.url_var.get().strip()
        if not url:
            messagebox.showwarning("SeaLoader", "Please paste a Workshop URL.")
            return

        self.fetch_btn.configure(state=tk.DISABLED)
        self.enable_btn.configure(state=tk.DISABLED)

        def task():
            try:
                ids = extract_required_item_ids(url)
                # Reuse installed cache to reduce API calls
                names_map = {i: self.installed_names_map[i] for i in ids if i in self.installed_names_map}
                ids_to_lookup = [i for i in ids if i not in names_map]
                if ids_to_lookup:
                    fetched = resolve_workshop_names(ids_to_lookup)
                    names_map.update(fetched)
            except Exception as exc:
                self.after(0, lambda: messagebox.showerror("SeaLoader", f"Failed to fetch IDs: {exc}"))
                self.after(0, lambda: self.fetch_btn.configure(state=tk.NORMAL))
                self.after(0, lambda: self.enable_btn.configure(state=tk.NORMAL))
                return

            def update():
                self.required_ids = ids
                self._refresh_required_list(names_map)
                self.fetch_btn.configure(state=tk.NORMAL)
                self.enable_btn.configure(state=tk.NORMAL)

            self.after(0, update)

        threading.Thread(target=task, daemon=True).start()

    def _refresh_required_list(self, names_map: Dict[str, str] | None = None) -> None:
        for row in self.required_tree.get_children():
            self.required_tree.delete(row)
        installed_ids = set(self.installed_mods.keys())
        names_map = names_map or {}
        for mod_id in self.required_ids:
            status = "Installed" if mod_id in installed_ids else "Missing"
            display_name = names_map.get(mod_id, "")
            self.required_tree.insert("", tk.END, values=(mod_id, display_name, status))

    def _on_enable(self) -> None:
        if not self.required_ids:
            messagebox.showinfo("SeaLoader", "No required mods loaded. Fetch from URL first.")
            return

        try:
            enabled_count, missing_count = enable_mods_in_ini(self.ini_path, self.required_ids)
        except Exception as exc:
            messagebox.showerror("SeaLoader", f"Failed to update INI: {exc}")
            return

        # Reload to reflect changes
        self._load_installed_mods()
        self._refresh_required_list()

        msg_lines = [f"Enabled {enabled_count} mod(s)."]
        if missing_count:
            msg_lines.append(f"{missing_count} required mod(s) not found in usersettings.ini.")
        else:
            msg_lines.append("All required mods were found.")
        messagebox.showinfo("SeaLoader", "\n".join(msg_lines))

    def _on_help(self) -> None:
        ini_hint = (
            f"Default INI: {self.ini_path}\n\n"
        )
        text = (
            "Usage:\n\n"
            "1) Paste a Steam Workshop URL at the top and click 'Fetch Required Mods'.\n"
            "   - Right list shows required Mod IDs, names and whether they are installed.\n"
            "2) Installed mods load automatically from usersettings.ini (left list).\n"
            "3) Click 'Enable Matching Mods' to turn on any installed required mods.\n"
            "   - A .bak backup of usersettings.ini is created before changes.\n"
            "4) Mods marked Missing must be installed separately in Sea Power.\n\n"
            "Tips:\n"
            "- Run with a custom INI path: python sealoader_gui.py \"<path-to-usersettings.ini>\"\n"
            "- Press F1 to open this help.\n\n"
            + ini_hint
        )
        messagebox.showinfo("SeaLoader Help", text)

    def _resource_path(self, name: str) -> Path:
        try:
            base = getattr(sys, "_MEIPASS", None)
            if base:
                return Path(base) / name
        except Exception:
            pass
        return Path(__file__).resolve().parent / name


def main() -> None:
    ini_path = DEFAULT_INI_PATH
    if len(sys.argv) > 1:
        ini_path = sys.argv[1]
    app = SeaLoaderApp(ini_path)
    app.mainloop()


if __name__ == "__main__":
    main()


