"""Configuration: non-secret knobs from config.toml at the project root.

Everything account-related lives in the environment (.env via direnv):
secrets (ANTHROPIC_API_KEY, BGG_PASSWORD, BGG_API_TOKEN) because they must
never touch disk or logs, and BGG_USERNAME — public, but kept there so the
account has exactly one home.
"""

from __future__ import annotations

import os
import tomllib
import warnings
from dataclasses import dataclass, replace
from pathlib import Path

DEFAULT_CONFIG_PATH = Path("config.toml")
DEFAULT_REVIEW_PORT = 8377
# Provenance marker filenames — the upload guard and both fixture
# generators must agree on these exactly.
STUB_CACHE_MARKER_NAME = "STUB_FIXTURES.marker"
STUB_DATA_MARKER_NAME = "STUB_DATA.marker"


@dataclass(frozen=True)
class Config:
    bgg_username: str = ""
    photos_dir: Path = Path("photos")
    data_dir: Path = Path("data")
    model: str = "claude-sonnet-5"
    rate_limit_seconds: float = 2.0

    @property
    def cache_dir(self) -> Path:
        return self.data_dir / "bgg_cache"

    @property
    def titles_path(self) -> Path:
        return self.data_dir / "titles.json"

    @property
    def unidentified_path(self) -> Path:
        return self.data_dir / "unidentified.json"

    @property
    def matches_path(self) -> Path:
        return self.data_dir / "matches.csv"

    @property
    def extract_raw_dir(self) -> Path:
        return self.data_dir / "extract_raw"

    @property
    def to_add_path(self) -> Path:
        return self.data_dir / "to_add.csv"

    @property
    def to_update_path(self) -> Path:
        return self.data_dir / "to_update.csv"

    @property
    def upload_log_path(self) -> Path:
        return self.data_dir / "upload_log.csv"

    @property
    def games_path(self) -> Path:
        return self.data_dir / "games.json"

    @property
    def title_removals_path(self) -> Path:
        # titles the human removed from the catalog (not a game, misread) —
        # filtered out of every titles.json rebuild
        return self.data_dir / "title_removals.json"

    @property
    def title_edits_path(self) -> Path:
        # human corrections to extracted reads (misspellings, known cues) —
        # replayed on every titles.json rebuild
        return self.data_dir / "title_edits.json"

    @property
    def title_splits_path(self) -> Path:
        # titles the human declared to be MULTIPLE physical copies: extract
        # and resolve dedupe must never cross-photo-merge them again
        return self.data_dir / "title_splits.json"

    @property
    def dismissed_path(self) -> Path:
        return self.data_dir / "unidentified_dismissed.json"

    @property
    def snapshot_paths(self) -> tuple[Path, Path]:
        return (
            self.data_dir / "collection_snapshot_base.xml",
            self.data_dir / "collection_snapshot_expansions.xml",
        )

    @property
    def storage_state_path(self) -> Path:
        # cwd-relative on purpose (credential-adjacent, gitignored) but
        # centralized here with every other artifact path
        return Path("storage_state.json")

    @property
    def stub_marker_paths(self) -> tuple[Path, Path]:
        # gitignored (travels with the stub XML) + committed (guards clones)
        return (
            self.cache_dir / STUB_CACHE_MARKER_NAME,
            self.data_dir / STUB_DATA_MARKER_NAME,
        )


def load_config(path: Path | None = None) -> Config:
    cfg = Config()
    p = path or DEFAULT_CONFIG_PATH
    if path is not None and not path.exists():
        # an EXPLICIT --config that doesn't exist must not silently fall
        # back to defaults — that reads photos/ while the user believes
        # their prod config is active
        raise FileNotFoundError(f"--config {path} does not exist")
    if p.exists():
        raw = tomllib.loads(p.read_text())
        known = {
            "photos_dir": Path,
            "data_dir": Path,
            "model": str,
            "rate_limit_seconds": float,
        }
        updates = {key: caster(raw[key]) for key, caster in known.items() if key in raw}
        if unknown := sorted(raw.keys() - known.keys()):
            # a typo'd key silently falling back to defaults is a debugging
            # trap ("No photos found in photos/") — say so up front
            warnings.warn(
                f"{p}: ignoring unknown key(s): {', '.join(unknown)}",
                stacklevel=2,
            )
        cfg = replace(cfg, **updates)
    if username := os.environ.get("BGG_USERNAME"):
        cfg = replace(cfg, bgg_username=username)
    return cfg
