# -*- coding: utf-8 -*-
"""최소 GUI (tkinter, 표준 라이브러리 — DECISIONS 2026-08-21).

exe 더블클릭 진입점. 하는 일:
  - 프로젝트별 변경·확인 이력 조회 (좌: 프로젝트 목록 / 우: 이력 표)
  - 루트 폴더 선택·변경, 감시 제외 폴더 추가/제거
  - [지금 검사] 수동 실행, 매일 07:00 자동 실행 등록, 브리핑 열기
감시 엔진은 건드리지 않는다 — cli.cmd_run을 그대로 부른다 (테스트 대상과 동일 경로).
"""

from __future__ import annotations

import argparse
import subprocess
import sys
import threading
import tkinter as tk
from pathlib import Path, PurePosixPath
from tkinter import filedialog, messagebox, ttk

import yaml

from watcher import __version__
from watcher.state import KIND_LABEL, State

CONFIG = Path("config.yaml")


def _load_cfg() -> dict:
    from watcher.cli import load_config
    return load_config(CONFIG)


def _save_cfg_keys(**updates) -> None:
    """config.yaml의 특정 키만 갱신해 다시 쓴다. (주석은 유지되지 않음)"""
    with open(CONFIG, "rb") as f:
        data = yaml.safe_load(f) or {}
    data.update(updates)
    CONFIG.write_text(
        yaml.safe_dump(data, allow_unicode=True, sort_keys=False, default_flow_style=False),
        encoding="utf-8")


def _ensure_config(parent) -> bool:
    """config.yaml이 없으면 폴더 선택 대화상자로 만들어준다."""
    if CONFIG.exists():
        return True
    messagebox.showinfo(
        "처음 실행", "감시할 루트 폴더를 선택해주세요.\n"
        "(프로젝트 폴더들이 들어 있는 최상위 폴더)", parent=parent)
    root = filedialog.askdirectory(title="감시할 루트 폴더 선택", parent=parent)
    if not root:
        return False
    from watcher.cli import write_initial_config
    write_initial_config(str(Path(root)), CONFIG)
    return True


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title(f"Drev v{__version__} — 산출물 변경 감시자")
        self.geometry("980x560")
        self.minsize(760, 420)
        if not _ensure_config(self):
            self.destroy()
            raise SystemExit(1)
        self.cfg = _load_cfg()
        self._scanning = False
        self._build()
        self.refresh()

    # ---------------------------------------------------------------- 화면 구성
    def _build(self):
        top = ttk.Frame(self, padding=8)
        top.pack(fill="x")
        self.root_var = tk.StringVar(value=f"감시 폴더: {', '.join(self.cfg['roots'])}")
        ttk.Label(top, textvariable=self.root_var).pack(side="left")
        ttk.Button(top, text="폴더 변경", command=self.change_root).pack(side="left", padx=4)
        ttk.Button(top, text="감시 제외 관리", command=self.manage_excludes).pack(side="left", padx=4)
        self.scan_btn = ttk.Button(top, text="▶ 지금 검사", command=self.run_scan)
        self.scan_btn.pack(side="right", padx=4)
        ttk.Button(top, text="매일 07:00 자동 실행", command=self.schedule_daily).pack(side="right", padx=4)

        mid = ttk.PanedWindow(self, orient="horizontal")
        mid.pack(fill="both", expand=True, padx=8, pady=4)

        left = ttk.Frame(mid)
        ttk.Label(left, text="프로젝트").pack(anchor="w")
        self.proj_list = tk.Listbox(left, exportselection=False, width=30)
        self.proj_list.pack(fill="both", expand=True)
        self.proj_list.bind("<<ListboxSelect>>", lambda e: self.fill_history())
        mid.add(left, weight=1)

        right = ttk.Frame(mid)
        ttk.Label(right, text="변경 이력 (최신순)").pack(anchor="w")
        cols = ("no", "at", "kind", "path", "ack")
        self.tree = ttk.Treeview(right, columns=cols, show="headings")
        for cid, text, w, anchor in [
            ("no", "번호", 50, "center"), ("at", "일시", 120, "center"),
            ("kind", "구분", 50, "center"), ("path", "파일", 420, "w"),
            ("ack", "확인", 200, "w"),
        ]:
            self.tree.heading(cid, text=text)
            self.tree.column(cid, width=w, anchor=anchor)
        ysb = ttk.Scrollbar(right, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=ysb.set)
        self.tree.pack(side="left", fill="both", expand=True)
        ysb.pack(side="right", fill="y")
        mid.add(right, weight=4)

        bottom = ttk.Frame(self, padding=8)
        bottom.pack(fill="x")
        ttk.Button(bottom, text="최신 브리핑 열기", command=self.open_latest_brief).pack(side="left")
        ttk.Button(bottom, text="브리핑 폴더", command=self.open_brief_dir).pack(side="left", padx=4)
        self.status = tk.StringVar(value="준비됨. [지금 검사]를 누르면 변경을 확인합니다.")
        ttk.Label(bottom, textvariable=self.status, foreground="#555").pack(side="right")

    # ---------------------------------------------------------------- 데이터
    def _history(self) -> list[dict]:
        db = Path(self.cfg["state_db"])
        if not db.exists():
            return []
        with State(db) as st:
            return st.history()

    def refresh(self):
        self.cfg = _load_cfg()
        self.root_var.set(f"감시 폴더: {', '.join(self.cfg['roots'])}")
        rows = self._history()
        by_proj: dict[str, list[dict]] = {}
        for r in rows:
            by_proj.setdefault(r["project"] or "미분류", []).append(r)
        sel = self.current_project()
        self.proj_list.delete(0, "end")
        self.proj_list.insert("end", f"전체 ({len(rows)})")
        self._proj_names = ["전체"]
        for proj in sorted(by_proj):
            unacked = sum(1 for r in by_proj[proj] if not r["ack_by"])
            self.proj_list.insert("end", f"{proj}  (변경 {len(by_proj[proj])} · 미확인 {unacked})")
            self._proj_names.append(proj)
        idx = self._proj_names.index(sel) if sel in self._proj_names else 0
        self.proj_list.selection_set(idx)
        self._rows = rows
        self.fill_history()

    def current_project(self) -> str:
        sel = self.proj_list.curselection() if hasattr(self, "_proj_names") else ()
        return self._proj_names[sel[0]] if sel else "전체"

    def fill_history(self):
        proj = self.current_project()
        self.tree.delete(*self.tree.get_children())
        for r in reversed(self._rows):
            if proj != "전체" and (r["project"] or "미분류") != proj:
                continue
            ack = (f"{r['ack_by']}" + (f" — {r['ack_note']}" if r["ack_note"] else "")) \
                if r["ack_by"] else "미확인"
            self.tree.insert("", "end", values=(
                r["change_id"], " ".join(r["detected_at"][:16].split("T")),
                KIND_LABEL[r["kind"]], r["path"], ack))

    # ---------------------------------------------------------------- 동작
    def run_scan(self):
        if self._scanning:
            return
        self._scanning = True
        self.scan_btn.configure(state="disabled")
        self.status.set("검사 중... (폴더 크기에 따라 시간이 걸릴 수 있습니다)")
        before = len(self._rows)

        def work():
            from watcher.cli import cmd_run
            args = argparse.Namespace(config=str(CONFIG), root=None, state_db=None,
                                      brief_dir=None, dry_run=False)
            try:
                rc = cmd_run(args)
            except Exception as e:  # GUI는 죽지 않는다
                rc, err = 1, str(e)
            else:
                err = None
            self.after(0, lambda: self._scan_done(rc, err, before))

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

    def _scan_done(self, rc: int, err: str | None, before: int):
        self._scanning = False
        self.scan_btn.configure(state="normal")
        self.refresh()
        new = len(self._rows) - before
        if rc == 0:
            self.status.set(f"검사 완료 — 새 변경 {new}건. "
                            + ("브리핑이 생성되었습니다." if new else "변경 없음."))
        else:
            self.status.set(f"검사 실패: {err or '콘솔 로그 확인'} (변경은 유실되지 않음)")

    def change_root(self):
        new = filedialog.askdirectory(title="감시할 루트 폴더 선택", parent=self)
        if not new:
            return
        _save_cfg_keys(roots=[str(Path(new))])
        self.refresh()
        self.status.set("루트 폴더가 변경되었습니다. [지금 검사]로 새 기준선을 만드세요.")

    def manage_excludes(self):
        dlg = tk.Toplevel(self)
        dlg.title("감시 제외 폴더")
        dlg.geometry("520x340")
        dlg.transient(self)
        ttk.Label(dlg, text="아래 폴더(하위 포함)는 감시하지 않습니다.",
                  padding=8).pack(anchor="w")
        lb = tk.Listbox(dlg)
        lb.pack(fill="both", expand=True, padx=8)
        excludes = list(self.cfg.get("exclude") or [])
        for e in excludes:
            lb.insert("end", e)

        def add():
            roots = [Path(r).resolve() for r in self.cfg["roots"]]
            d = filedialog.askdirectory(title="제외할 폴더 (감시 폴더 아래)",
                                        initialdir=roots[0] if roots else None, parent=dlg)
            if not d:
                return
            rel = None
            target = Path(d).resolve()
            for root in roots:
                try:
                    rel = PurePosixPath(target.relative_to(root)).as_posix()
                    if len(roots) > 1:
                        rel = f"{root.name}/{rel}"
                    break
                except ValueError:
                    continue
            if rel is None:
                messagebox.showwarning("범위 밖", "감시 폴더 아래의 폴더만 제외할 수 있습니다.",
                                       parent=dlg)
                return
            if rel not in excludes:
                excludes.append(rel)
                lb.insert("end", rel)

        def drop():
            sel = lb.curselection()
            if sel:
                excludes.pop(sel[0])
                lb.delete(sel[0])

        def save():
            _save_cfg_keys(exclude=excludes)
            self.refresh()
            self.status.set(f"제외 폴더 {len(excludes)}개 저장됨. 다음 검사부터 적용됩니다.")
            dlg.destroy()

        btns = ttk.Frame(dlg, padding=8)
        btns.pack(fill="x")
        ttk.Button(btns, text="폴더 추가...", command=add).pack(side="left")
        ttk.Button(btns, text="선택 제거", command=drop).pack(side="left", padx=4)
        ttk.Button(btns, text="저장", command=save).pack(side="right")

    def schedule_daily(self):
        exe_dir = Path.cwd()
        bat = exe_dir / "run_daily.bat"
        if not bat.exists():
            bat.write_text('@echo off\r\ncd /d "%~dp0"\r\n'
                           "project-watcher.exe run >> run_daily.log 2>&1\r\n",
                           encoding="ascii")
        r = subprocess.run(["schtasks", "/Create", "/TN", "ProjectWatcher",
                            "/TR", f'"{bat}"', "/SC", "DAILY", "/ST", "07:00", "/F"],
                           capture_output=True)
        if r.returncode == 0:
            messagebox.showinfo("등록 완료", "매일 07:00 자동 실행이 등록되었습니다.\n"
                                "(해제: 작업 스케줄러에서 ProjectWatcher 삭제)", parent=self)
        else:
            messagebox.showwarning("등록 실패", "자동 등록에 실패했습니다 (권한 문제일 수 있음).\n"
                                   f"관리자 cmd에서:\nschtasks /Create /TN ProjectWatcher "
                                   f'/TR "\\"{bat}\\"" /SC DAILY /ST 07:00', parent=self)

    def open_latest_brief(self):
        brief_dir = Path(self.cfg["brief_dir"])
        briefs = sorted(brief_dir.glob("brief_*.html"),
                        key=lambda p: p.stat().st_mtime) if brief_dir.is_dir() else []
        if not briefs:
            messagebox.showinfo("브리핑 없음", "아직 브리핑이 없습니다. [지금 검사]를 먼저 실행하세요.",
                                parent=self)
            return
        import os
        os.startfile(briefs[-1])

    def open_brief_dir(self):
        brief_dir = Path(self.cfg["brief_dir"])
        brief_dir.mkdir(parents=True, exist_ok=True)
        import os
        os.startfile(brief_dir)


def run_gui() -> int:
    if getattr(sys, "frozen", False):
        import os
        os.chdir(Path(sys.executable).parent)
        try:  # GUI 모드에선 콘솔 창을 숨긴다
            import ctypes
            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
            if hwnd:
                ctypes.windll.user32.ShowWindow(hwnd, 0)
        except Exception:
            pass
    app = App()
    app.mainloop()
    return 0
