"""`bggpipe init` — guided first-run setup, idempotent like every stage.

Inspects the working directory and only fills gaps: creates photos/ and
data/, writes a default config.toml when none exists, prompts for the
credentials missing from the environment and .env (hidden input, appended
to .env with 0600 permissions — values never echo and never reach logs),
and offers the one-time Playwright browser download. Re-running reports
status and prompts only for what is still missing; without a TTY it
prints the status report and exits instead of hanging on a prompt.
"""

from __future__ import annotations

import os
import subprocess
import sys
from collections.abc import Callable
from dataclasses import dataclass, field
from pathlib import Path

import typer

from bggpipe.config import Config

CONFIG_TEMPLATE = """\
# Non-secret knobs for bggpipe. Everything account-related — including
# your BGG username — lives in .env (see .env.example), not here.

photos_dir = "photos"
data_dir = "data"
model = "claude-sonnet-5"
rate_limit_seconds = 2.0
"""

ENV_HEADER = """\
# bggpipe credentials — this file must stay out of version control.
"""

# (key, secret?, why it's needed, where to get it)
ENV_KEYS = (
    (
        "ANTHROPIC_API_KEY",
        True,
        "vision extraction (stage 1)",
        "https://console.anthropic.com/",
    ),
    (
        "BGG_USERNAME",
        False,
        "diff/upload/enrich",
        "your boardgamegeek.com account name",
    ),
    (
        "BGG_PASSWORD",
        True,
        "upload's website login (stage 5)",
        "your boardgamegeek.com password",
    ),
    (
        "BGG_API_TOKEN",
        True,
        "resolve/diff/enrich (stages 2, 4, 6)",
        "https://boardgamegeek.com/applications — approval takes a week+, "
        "apply early; everything except upload works while you wait",
    ),
)


@dataclass
class InitReport:
    created_dirs: list[str] = field(default_factory=list)
    wrote_config: bool = False
    keys_ready: list[str] = field(default_factory=list)
    keys_written: list[str] = field(default_factory=list)
    keys_missing: list[str] = field(default_factory=list)
    browser_installed: bool = False


def _env_file_keys(env_path: Path) -> set[str]:
    """Key names with non-empty values in .env. Handles `export KEY=v` and
    quoted values; a quoted-empty value ("" / '') counts as NOT set. Values
    never outlive this parse and are never printed."""
    if not env_path.exists():
        return set()
    present = set()
    for line in env_path.read_text().splitlines():
        line = line.strip()
        if "=" not in line or line.startswith("#"):
            continue
        key, _, value = line.partition("=")
        key = key.strip()
        if key.startswith("export "):
            key = key.removeprefix("export ").strip()
        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
            value = value[1:-1]
        if value:
            present.add(key)
    return present


def _quote_env_value(value: str) -> str:
    """Single-quote for `source`/direnv safety: spaces, $, backslashes and
    quotes must survive the shell verbatim."""
    return "'" + value.replace("'", "'\\''") + "'"


def _default_secret_prompt(label: str) -> str:
    return typer.prompt(label, default="", show_default=False, hide_input=True)


def _default_plain_prompt(label: str) -> str:
    return typer.prompt(label, default="", show_default=False)


def _default_install_browser() -> bool:
    result = subprocess.run(  # noqa: S603 — fixed argv, no shell
        [sys.executable, "-m", "playwright", "install", "chromium"],
        check=False,
    )
    return result.returncode == 0


def run_init(
    cfg: Config,
    *,
    project_dir: Path | None = None,
    interactive: bool | None = None,
    plain_prompt: Callable[[str], str] = _default_plain_prompt,
    secret_prompt: Callable[[str], str] = _default_secret_prompt,
    confirm: Callable[[str], bool] | None = None,
    install_browser: Callable[[], bool] = _default_install_browser,
) -> InitReport:
    project_dir = project_dir or Path.cwd()
    if interactive is None:
        interactive = sys.stdin.isatty()
    if confirm is None:
        confirm = lambda label: typer.confirm(label, default=True)  # noqa: E731

    report = InitReport()
    typer.echo("bggpipe setup — checks what exists, fills only the gaps.\n")

    # -- directories ----------------------------------------------------
    for path in (project_dir / cfg.photos_dir, project_dir / cfg.data_dir):
        if not path.is_dir():
            path.mkdir(parents=True)
            report.created_dirs.append(str(path))
            typer.echo(f"  created {path}/")
        else:
            typer.echo(f"  found {path}/")

    # -- config.toml ----------------------------------------------------
    config_path = project_dir / "config.toml"
    if not config_path.exists():
        config_path.write_text(CONFIG_TEMPLATE)
        report.wrote_config = True
        typer.echo(f"  wrote {config_path} (defaults — edit if you like)")
    else:
        typer.echo(f"  found {config_path}")

    # -- credentials ----------------------------------------------------
    env_path = project_dir / ".env"
    in_file = _env_file_keys(env_path)
    for key, secret, why, where in ENV_KEYS:
        if os.environ.get(key) or key in in_file:
            report.keys_ready.append(key)
            typer.echo(f"  {key}: set")
            continue
        if not interactive:
            report.keys_missing.append(key)
            continue
        typer.echo(f"\n  {key} — needed for {why}\n  ({where})")
        prompt = secret_prompt if secret else plain_prompt
        value = prompt(f"  {key} (enter to skip)").strip()
        if not value:
            report.keys_missing.append(key)
            continue
        is_new_file = not env_path.exists()
        # owner-only from the FIRST byte — chmod-after-write leaves a
        # world-readable window holding a credential
        fd = os.open(env_path, os.O_CREAT | os.O_WRONLY | os.O_APPEND, 0o600)
        with os.fdopen(fd, "a") as f:
            if is_new_file:
                f.write(ENV_HEADER)
            f.write(f"{key}={_quote_env_value(value)}\n")
        env_path.chmod(0o600)  # older files created by hand tighten up too
        report.keys_written.append(key)
        typer.echo(f"  {key}: saved to {env_path}")

    # -- browser --------------------------------------------------------
    if interactive and confirm(
        "\nDownload/verify the Chromium browser for the upload stage? "
        "(one-time, ~100MB; skippable)"
    ):
        report.browser_installed = install_browser()
        if not report.browser_installed:
            typer.echo("  browser install failed — re-run init to retry")

    # -- summary --------------------------------------------------------
    typer.echo("\nStatus:")
    for key in report.keys_ready + report.keys_written:
        typer.echo(f"  ready    {key}")
    for key in report.keys_missing:
        typer.echo(f"  missing  {key}")
    if report.keys_missing:
        typer.echo(
            "\nMissing keys are fine to start: extract only needs "
            "ANTHROPIC_API_KEY, and re-running `bggpipe init` prompts for "
            "the rest whenever you have them."
        )
    typer.echo(
        "\nNext: drop shelf photos into "
        f"{project_dir / cfg.photos_dir}/ and run `bggpipe extract`."
    )
    if env_path.exists() and not os.environ.get("BGG_USERNAME"):
        typer.echo(
            "Load .env into your shell first: `direnv allow` (if you use "
            "direnv) or `set -a; source .env; set +a`."
        )
    return report
