"""Named factory seat presets and isolated pi account composition."""

from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import threading

import pytest

from adw_modules import accounts, agents
from adw_modules.data_types import AgentConfig, PromptEngineering, SSSFConfig


CENTRAL = """
defaults:
  model: central/model
  thinking: medium
  tools: [read]
agents:
  - name: planner
    purpose: plan
    prompt_engineering:
      system: prompts/planner-system.md
      user: prompts/planner-user.md
  - name: builder
    purpose: build
    prompt_engineering:
      system: prompts/builder-system.md
      user: prompts/builder-user.md
  - name: scout
    purpose: scout
    prompt_engineering:
      system: prompts/scout-system.md
      user: prompts/scout-user.md
  - name: reviewer
    purpose: review
    prompt_engineering:
      system: prompts/reviewer-system.md
      user: prompts/reviewer-user.md
  - name: documenter
    purpose: docs
    prompt_engineering:
      system: prompts/documenter-system.md
      user: prompts/documenter-user.md
"""


def _factory(tmp_path: Path, monkeypatch) -> Path:
    factory = tmp_path / "factory"
    (factory / "presets").mkdir(parents=True)
    (factory / "sssf.config.yaml").write_text(CENTRAL)
    monkeypatch.setenv("FACTORY_ROOT", str(factory))
    return factory


def test_preset_overlays_named_seats_without_truncating_roster(tmp_path: Path, monkeypatch) -> None:
    factory = _factory(tmp_path, monkeypatch)
    (factory / "presets" / "fast.yaml").write_text(
        "seats:\n  builder:\n    model: fast/model\n    thinking: low\n"
    )

    cfg = agents.load_config(str(factory / "sssf.config.yaml"), "fast")

    assert cfg.preset == "fast"
    assert [agent.name for agent in cfg.agents] == ["planner", "builder", "scout", "reviewer", "documenter"]
    assert cfg.agents[0].prompt_engineering.system.endswith("planner-system.md")
    assert cfg.agents[1].model == "fast/model"
    assert cfg.agents[1].thinking == "low"


def test_unknown_preset_and_seat_fail_closed(tmp_path: Path, monkeypatch) -> None:
    factory = _factory(tmp_path, monkeypatch)
    central = factory / "sssf.config.yaml"
    with pytest.raises(SystemExit, match="available: \\[\\]"):
        agents.load_config(str(central), "missing")

    (factory / "presets" / "broken.yaml").write_text("seats:\n  missing:\n    model: nope\n")
    with pytest.raises(SystemExit, match="preset seat 'missing'.*builder.*planner"):
        agents.load_config(str(central), "broken")


def test_account_materialization_composes_source_without_mutating_auth(tmp_path: Path, monkeypatch) -> None:
    monkeypatch.setenv("HOME", str(tmp_path))
    source = tmp_path / ".pi" / "agent"
    source.mkdir(parents=True)
    auth = source / "auth.json"
    auth.write_text("global-auth")
    (source / "models.json").write_text("models")
    (source / "sessions").mkdir()
    account_auth = (tmp_path / ".local/state/overdeck/systray/runtime/accounts/acct/PI_HOME/auth.json")
    account_auth.parent.mkdir(parents=True)
    account_auth.write_text("account-auth")

    target = accounts.materialize_account("acct")

    assert auth.read_text() == "global-auth"
    assert (target / "models.json").is_symlink()
    assert (target / "sessions").is_symlink()
    assert (target / "auth.json").resolve() == account_auth


def test_same_account_runs_keep_independent_directories(tmp_path: Path, monkeypatch) -> None:
    monkeypatch.setenv("HOME", str(tmp_path))
    source = tmp_path / ".pi" / "agent"
    source.mkdir(parents=True)
    (source / "models.json").write_text("models")
    account_auth = (tmp_path / ".local/state/overdeck/systray/runtime/accounts/acct/PI_HOME/auth.json")
    account_auth.parent.mkdir(parents=True)
    account_auth.write_text("account-auth")
    barrier = threading.Barrier(2)

    def materialize() -> Path:
        barrier.wait(timeout=2)
        return accounts.materialize_account("acct")

    with ThreadPoolExecutor(max_workers=2) as executor:
        first, second = [future.result(timeout=2)
                         for future in (executor.submit(materialize), executor.submit(materialize))]

    assert first != second
    for target in (first, second):
        assert (target / "models.json").is_symlink()
        assert (target / "auth.json").resolve() == account_auth
    accounts.cleanup_account(first)
    assert not first.exists()
    assert second.is_dir()
    accounts.cleanup_account(second)
    assert not second.exists()


def test_unknown_account_fails_closed(tmp_path: Path, monkeypatch) -> None:
    monkeypatch.setenv("HOME", str(tmp_path))

    with pytest.raises(SystemExit, match="account 'missing' is not defined"):
        accounts.materialize_account("missing")
    monkeypatch.setenv("HOME", str(tmp_path))

    with pytest.raises(SystemExit, match="account 'missing' is not defined"):
        accounts.materialize_account("missing")


def test_validation_resolves_models_from_configured_account(tmp_path: Path, monkeypatch) -> None:
    system = tmp_path / "system.md"
    user = tmp_path / "user.md"
    system.write_text("system")
    user.write_text("user")
    cfg = SSSFConfig(account="acct", agents=[AgentConfig(
        name="planner", model="test/model",
        prompt_engineering=PromptEngineering(system=str(system), user=str(user)),
    )])
    resolved = []
    monkeypatch.setattr(agents.agent_pi, "resolve_model",
                        lambda model, account: resolved.append((model, account)))

    agents.validate(cfg, ["planner"])

    assert resolved == [("test/model", "acct")]


@pytest.mark.parametrize(
    ("preset", "model"),
    [
        ("gpt-sol-medium", "gpt/sol-web-medium"),
        ("gpt-sol-pro", "gpt/sol-web-pro"),
    ],
)
def test_gpt_presets_overlay_all_factory_seats_without_changing_accounts(
    preset: str, model: str,
) -> None:
    factory_root = Path(__file__).resolve().parents[1]
    cfg = agents.load_config(str(factory_root / "sssf.config.yaml"), preset)

    assert cfg.account is None
    assert [agent.name for agent in cfg.agents] == ["planner", "builder", "scout", "reviewer", "documenter"]
    assert {agent.model for agent in cfg.agents} == {model}
    assert {agent.thinking for agent in cfg.agents} == {"off"}
