"""Tests for brokr passwd, export, version, and enhanced doctor."""

import json
import os
from pathlib import Path

from typer.testing import CliRunner

from brokr.cli import app
from brokr.store import load_store, save_store

runner = CliRunner()
PASSPHRASE = "testpassword"


def _init_store(tmp_path, monkeypatch, passphrase=PASSPHRASE):
    monkeypatch.setenv("BROKR_DIR", str(tmp_path))
    monkeypatch.setenv("BROKR_PASSPHRASE", passphrase)
    runner.invoke(app, ["init"], input=f"{passphrase}\n{passphrase}\n")


def _store_with_secrets(tmp_path, monkeypatch, passphrase=PASSPHRASE):
    _init_store(tmp_path, monkeypatch, passphrase)
    data = load_store(passphrase, store_dir=tmp_path)
    new_data = {
        **data,
        "secrets": {"API_KEY": "sk-abc123", "DB_URL": "postgres://localhost/db"},
        "projects": {
            "my-app": {
                "secrets": ["API_KEY"],
                "agents": {"deploy-bot": ["API_KEY"]},
            }
        },
    }
    save_store(new_data, passphrase, store_dir=tmp_path)


class TestVersion:
    def test_version_flag(self, tmp_path, monkeypatch):
        result = runner.invoke(app, ["--version"])
        assert result.exit_code == 0
        assert "brokr" in result.output


class TestPasswd:
    def test_changes_passphrase(self, tmp_path, monkeypatch):
        _store_with_secrets(tmp_path, monkeypatch)

        # passwd uses get_passphrase() for old pass — which reads BROKR_PASSPHRASE
        # Then prompts for new passphrase via input
        result = runner.invoke(
            app,
            ["passwd"],
            input="newpassword\nnewpassword\n",
        )
        assert result.exit_code == 0
        assert "changed successfully" in result.output

        # Old passphrase should fail
        from brokr.store import BrokrError
        import pytest

        with pytest.raises(BrokrError):
            load_store(PASSPHRASE, store_dir=tmp_path)

        # New passphrase should work
        data = load_store("newpassword", store_dir=tmp_path)
        assert data["secrets"]["API_KEY"] == "sk-abc123"

    def test_mismatched_new_passphrases(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)

        result = runner.invoke(
            app,
            ["passwd"],
            input="new1\nnew2\n",
        )
        assert result.exit_code == 1
        assert "do not match" in result.output

    def test_wrong_old_passphrase(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        # Set wrong passphrase in env
        monkeypatch.setenv("BROKR_PASSPHRASE", "wrongpass")

        result = runner.invoke(
            app,
            ["passwd"],
            input="new\nnew\n",
        )
        assert result.exit_code == 1

    def test_no_store_errors(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["passwd"])
        assert result.exit_code == 1
        assert "No store found" in result.output

    def test_short_passphrase_warns(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)

        result = runner.invoke(
            app,
            ["passwd"],
            input="short\nshort\n",
        )
        assert result.exit_code == 0
        assert "short" in result.output

    def test_preserves_projects(self, tmp_path, monkeypatch):
        _store_with_secrets(tmp_path, monkeypatch)

        result = runner.invoke(
            app,
            ["passwd"],
            input="newpassword\nnewpassword\n",
        )
        assert result.exit_code == 0

        data = load_store("newpassword", store_dir=tmp_path)
        assert "my-app" in data["projects"]
        assert "deploy-bot" in data["projects"]["my-app"]["agents"]


class TestExport:
    def test_env_format(self, tmp_path, monkeypatch):
        _store_with_secrets(tmp_path, monkeypatch)

        result = runner.invoke(app, ["export", "--yes"])
        assert result.exit_code == 0
        assert 'API_KEY="sk-abc123"' in result.output
        assert 'DB_URL="postgres://localhost/db"' in result.output

    def test_json_format(self, tmp_path, monkeypatch):
        _store_with_secrets(tmp_path, monkeypatch)

        result = runner.invoke(app, ["export", "--format", "json", "--yes"])
        assert result.exit_code == 0
        # Output includes stderr passphrase warning; find the JSON portion
        output_lines = result.output.strip().split("\n")
        json_start = next(i for i, line in enumerate(output_lines) if line.strip().startswith("{"))
        json_text = "\n".join(output_lines[json_start:])
        parsed = json.loads(json_text)
        assert parsed["API_KEY"] == "sk-abc123"
        assert parsed["DB_URL"] == "postgres://localhost/db"

    def test_unknown_format(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)

        result = runner.invoke(app, ["export", "--format", "xml", "--yes"])
        assert result.exit_code == 1
        assert "Unknown format" in result.output

    def test_empty_store(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)

        result = runner.invoke(app, ["export", "--yes"])
        assert result.exit_code == 0
        # Output may contain passphrase warning but no KEY= lines
        for line in result.output.strip().split("\n"):
            assert "=" not in line or "BROKR_PASSPHRASE" in line or "Using passphrase" in line

    def test_env_escapes_quotes(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        data = load_store(PASSPHRASE, store_dir=tmp_path)
        new_data = {**data, "secrets": {"QUOTED": 'val"with"quotes'}}
        save_store(new_data, PASSPHRASE, store_dir=tmp_path)

        result = runner.invoke(app, ["export", "--yes"])
        assert '\\"' in result.output


class TestEnhancedDoctor:
    def test_reports_dangling_project_refs(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        # Create store with a project referencing a deleted secret
        data = {
            "secrets": {},
            "projects": {
                "my-app": {
                    "secrets": ["GONE"],
                    "agents": {"bot": ["GONE"]},
                }
            },
        }
        save_store(data, PASSPHRASE, store_dir=tmp_path)

        result = runner.invoke(app, ["doctor"])
        assert "deleted secret" in result.output
        assert "GONE" in result.output

    def test_reports_secret_and_project_counts(self, tmp_path, monkeypatch):
        _store_with_secrets(tmp_path, monkeypatch)

        result = runner.invoke(app, ["doctor"])
        assert "2 secrets" in result.output
        assert "1 projects" in result.output

    def test_file_permissions_checked(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)

        # File permissions should be 0o600 after init
        store_file = tmp_path / "store.enc"
        salt_file = tmp_path / "salt"
        assert store_file.stat().st_mode & 0o777 == 0o600
        assert salt_file.stat().st_mode & 0o777 == 0o600

        result = runner.invoke(app, ["doctor"])
        assert "brokr is healthy" in result.output

    def test_bad_brokr_file_detected(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        # Create .brokr pointing to nonexistent project
        (tmp_path / ".brokr").write_text("ghost-project\n")
        monkeypatch.chdir(tmp_path)

        result = runner.invoke(app, ["doctor"])
        assert "unknown project" in result.output


class TestPassphraseStrength:
    def test_short_passphrase_warns(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["init"], input="short\nshort\n")
        assert result.exit_code == 0
        assert "short" in result.output  # passphrase strength warning

    def test_long_passphrase_no_warning(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["init"], input="longpassphrase\nlongpassphrase\n")
        assert result.exit_code == 0
        # Should not contain the strength warning
        assert "Consider using a stronger" not in result.output
