"""
PhotoPick desktop shell: start local FastAPI, open a native window, stop on quit.
"""
from __future__ import annotations

import os
import shutil
import socket
import sys
import threading
import time
import urllib.error
import urllib.request
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent
APP_SUPPORT = Path.home() / "Library" / "Application Support" / "PhotoPick"
PREFERRED_PORT = int(os.environ.get("PHOTOPICK_PORT", "8787"))


def _app_version() -> str:
    from backend import config
    return config.app_version()


def _license_env_line_is_loopback(line: str) -> bool:
    """True when a .env line sets LICENSE_API_BASE to localhost / 127.0.0.1."""
    stripped = line.strip()
    if not stripped or stripped.startswith("#"):
        return False
    if not stripped.startswith("LICENSE_API_BASE="):
        return False
    raw = stripped.split("=", 1)[1].strip().strip("'").strip('"')
    from urllib.parse import urlparse

    host = (urlparse(raw).hostname or "").lower()
    return host in {"127.0.0.1", "localhost", "::1"}


def _sanitize_app_support_env() -> None:
    """Remove loopback LICENSE_API_BASE left by older packaged builds.

    Do not copy App-bundled / project .env into Application Support — that once
    shipped localhost + personal keys to users. Product defaults live in
    config.py; only user-owned keys belong under Application Support.
    """
    path = APP_SUPPORT / ".env"
    if not path.is_file():
        return
    try:
        text = path.read_text(encoding="utf-8")
    except OSError:
        return
    lines = text.splitlines(keepends=True)
    kept = [ln for ln in lines if not _license_env_line_is_loopback(ln)]
    if len(kept) == len(lines):
        return
    try:
        path.write_text("".join(kept), encoding="utf-8")
    except OSError:
        pass


def _migrate_once() -> None:
    """Seed Application Support with project data on first desktop launch."""
    APP_SUPPORT.mkdir(parents=True, exist_ok=True)
    (APP_SUPPORT / "previews").mkdir(parents=True, exist_ok=True)

    # Never migrate .env into Application Support (see _sanitize_app_support_env).
    _sanitize_app_support_env()

    project_store = PROJECT_ROOT / "data" / "photopick.json"
    app_store = APP_SUPPORT / "photopick.json"
    if not app_store.exists() and project_store.exists():
        shutil.copy2(project_store, app_store)
        project_previews = PROJECT_ROOT / "data" / "previews"
        app_previews = APP_SUPPORT / "previews"
        if project_previews.is_dir():
            for item in project_previews.iterdir():
                dest = app_previews / item.name
                if dest.exists():
                    continue
                if item.is_dir():
                    shutil.copytree(item, dest)
                else:
                    shutil.copy2(item, dest)


def _prepare_env() -> None:
    os.environ.setdefault("PHOTOPICK_DESKTOP", "1")
    # Ensure imports resolve when launched via .app (cwd may vary).
    os.chdir(PROJECT_ROOT)
    if str(PROJECT_ROOT) not in sys.path:
        sys.path.insert(0, str(PROJECT_ROOT))

    if "PHOTOPICK_DATA_DIR" in os.environ:
        # Explicit override (isolated packaging tests) — use it as-is and
        # skip the real Application Support migration entirely, so testing
        # a built .app can never touch a real user's live data.
        Path(os.environ["PHOTOPICK_DATA_DIR"]).mkdir(parents=True, exist_ok=True)
        return

    try:
        _migrate_once()
        os.environ["PHOTOPICK_DATA_DIR"] = str(APP_SUPPORT)
    except OSError as exc:
        # Fall back to project data/ if Application Support is unavailable.
        fallback = PROJECT_ROOT / "data"
        fallback.mkdir(parents=True, exist_ok=True)
        os.environ.setdefault("PHOTOPICK_DATA_DIR", str(fallback))
        print(f"無法使用 Application Support（{exc}），改用 {fallback}", file=sys.stderr)


def _find_free_port(preferred: int = PREFERRED_PORT, span: int = 30) -> int:
    for port in range(preferred, preferred + span):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            try:
                sock.bind(("127.0.0.1", port))
                return port
            except OSError:
                continue
    raise RuntimeError(f"找不到可用埠（試過 {preferred}–{preferred + span - 1}）")


def _wait_ready(url: str, timeout: float = 30.0) -> None:
    deadline = time.time() + timeout
    last_err = None
    while time.time() < deadline:
        try:
            with urllib.request.urlopen(url, timeout=1) as resp:
                if 200 <= resp.status < 500:
                    return
        except (urllib.error.URLError, TimeoutError, ConnectionError) as exc:
            last_err = exc
            time.sleep(0.1)
    raise RuntimeError(f"伺服器啟動逾時：{last_err}")


def main() -> int:
    _prepare_env()

    try:
        import webview
    except ImportError:
        print("缺少 pywebview，請執行：.venv/bin/pip install pywebview", file=sys.stderr)
        return 1

    # Import after PHOTOPICK_DATA_DIR is set so config picks Application Support.
    from backend.main import app
    import uvicorn

    port = _find_free_port()
    url = f"http://127.0.0.1:{port}/"
    log_path = Path(os.environ["PHOTOPICK_DATA_DIR"]) / "server.log"

    config = uvicorn.Config(
        app,
        host="127.0.0.1",
        port=port,
        log_level="info",
        access_log=False,
    )
    server = uvicorn.Server(config)

    def _run_server() -> None:
        try:
            with open(log_path, "a", encoding="utf-8") as logf:
                logf.write(f"\n--- desktop start port={port} ---\n")
        except OSError:
            pass
        server.run()

    thread = threading.Thread(target=_run_server, name="photopick-uvicorn", daemon=True)
    thread.start()

    try:
        _wait_ready(url)
    except Exception as exc:
        server.should_exit = True
        print(f"啟動失敗：{exc}\n詳見 {log_path}", file=sys.stderr)
        return 1

    version = _app_version()
    window = webview.create_window(
        title=f"PhotoPick v{version}",
        url=url,
        width=1280,
        height=860,
        min_size=(900, 600),
        text_select=True,
    )
    webview.start()

    # Window closed → stop backend.
    server.should_exit = True
    thread.join(timeout=5)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
