"""Tests for brokr CLI commands and passphrase handling."""

import os
import sys

import pytest
from typer.testing import CliRunner

from brokr.cli import app, build_run_env, get_passphrase, mask_value

runner = CliRunner()


class TestInit:
    def test_creates_store_and_salt(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["init"], input="secret\nsecret\n")
        assert result.exit_code == 0
        assert (tmp_path / "store.enc").exists()
        assert (tmp_path / "salt").exists()
        assert len((tmp_path / "salt").read_bytes()) == 16
        assert "brokr initialized" in result.output

    def test_mismatched_passphrases(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["init"], input="one\ntwo\n")
        assert result.exit_code == 1
        assert "do not match" in result.output

    def test_overwrite_confirm_abort(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        runner.invoke(app, ["init"], input="secret\nsecret\n")
        assert (tmp_path / "store.enc").exists()
        result = runner.invoke(app, ["init"], input="n\n")
        assert result.exit_code != 0

    def test_overwrite_confirm_yes(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        runner.invoke(app, ["init"], input="secret\nsecret\n")
        result = runner.invoke(app, ["init"], input="y\nnewpass\nnewpass\n")
        assert result.exit_code == 0
        assert "brokr initialized" in result.output

    def test_store_is_loadable(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        runner.invoke(app, ["init"], input="testpw\ntestpw\n")
        from brokr.store import load_store

        data = load_store("testpw", store_dir=tmp_path)
        assert data == {"secrets": {}, "projects": {}}


class TestGetPassphrase:
    def test_env_var_used(self, monkeypatch, capsys):
        monkeypatch.setenv("BROKR_PASSPHRASE", "from-env")
        result = get_passphrase()
        assert result == "from-env"

    def test_env_var_warning_printed(self, monkeypatch, capsys):
        monkeypatch.setenv("BROKR_PASSPHRASE", "from-env")
        get_passphrase()
        captured = capsys.readouterr()
        assert "BROKR_PASSPHRASE" in captured.err


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


class TestSet:
    def test_set_stores_value(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["set", "API_KEY", "--value", "my-secret"])
        assert result.exit_code == 0
        assert "Secret API_KEY stored." in result.output

    def test_set_and_load_back(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "DB_PASS", "--value", "hunter2"])
        from brokr.store import load_store

        data = load_store("secret", store_dir=tmp_path)
        assert data["secrets"]["DB_PASS"] == "hunter2"

    def test_set_prompts_for_value(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["set", "TOKEN"], input="prompted-val\n")
        assert result.exit_code == 0
        assert "Secret TOKEN stored." in result.output

    def test_set_invalid_key_name(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["set", "bad-key", "--value", "x"])
        assert result.exit_code != 0

    def test_set_updates_existing(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "KEY", "--value", "old"])
        runner.invoke(app, ["set", "KEY", "--value", "new"])
        from brokr.store import load_store

        data = load_store("secret", store_dir=tmp_path)
        assert data["secrets"]["KEY"] == "new"


class TestGet:
    def test_get_returns_value(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "MY_SECRET", "--value", "abc123"])
        result = runner.invoke(app, ["get", "MY_SECRET"])
        assert result.exit_code == 0
        assert "abc123" in result.output

    def test_get_missing_key(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["get", "NOPE"])
        assert result.exit_code == 1
        assert "not found" in result.output


class TestList:
    def test_list_empty(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["list"])
        assert result.exit_code == 0
        assert "No secrets stored." in result.output

    def test_list_shows_keys(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "ALPHA", "--value", "value-alpha-long"])
        runner.invoke(app, ["set", "BETA", "--value", "value-beta-longx"])
        result = runner.invoke(app, ["list"])
        assert result.exit_code == 0
        assert "ALPHA" in result.output
        assert "BETA" in result.output
        assert "value-alpha-long" not in result.output

    def test_list_show_flag(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "VISIBLE", "--value", "plaintext-value"])
        result = runner.invoke(app, ["list", "--show"])
        assert result.exit_code == 0
        assert "plaintext-value" in result.output

    def test_mask_short(self):
        assert mask_value("short") == "****"

    def test_mask_long(self):
        assert mask_value("sk-abcdefgh1234a1b2") == "sk-...a1b2"


class TestRm:
    def test_rm_removes_key(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        runner.invoke(app, ["set", "GONE", "--value", "bye"])
        result = runner.invoke(app, ["rm", "GONE"])
        assert result.exit_code == 0
        assert "Secret GONE removed." in result.output
        from brokr.store import load_store

        data = load_store("secret", store_dir=tmp_path)
        assert "GONE" not in data["secrets"]

    def test_rm_missing_key(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["rm", "MISSING"])
        assert result.exit_code == 1
        assert "not found" in result.output


class TestDoctor:
    def test_no_store_dir(self, tmp_path, monkeypatch):
        missing = tmp_path / "nonexistent"
        monkeypatch.setenv("BROKR_DIR", str(missing))
        result = runner.invoke(app, ["doctor"])
        assert result.exit_code == 0
        assert "✗" in result.output
        assert "Store directory missing" in result.output
        assert "brokr init" in result.output
        assert "store.enc" not in result.output

    def test_store_dir_exists_but_no_files(self, tmp_path, monkeypatch):
        monkeypatch.setenv("BROKR_DIR", str(tmp_path))
        result = runner.invoke(app, ["doctor"])
        assert result.exit_code == 0
        assert "Store directory exists" in result.output
        assert "store.enc missing" in result.output
        assert "salt missing" in result.output
        assert "brokr is healthy" not in result.output

    def test_healthy_store(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["doctor"])
        assert result.exit_code == 0
        assert "Store directory exists" in result.output
        assert "store.enc exists" in result.output
        assert "salt exists" in result.output
        assert "Store decrypts successfully" in result.output
        assert "brokr is healthy" in result.output

    def test_bad_passphrase(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        monkeypatch.setenv("BROKR_PASSPHRASE", "wrong")
        result = runner.invoke(app, ["doctor"])
        assert result.exit_code == 0
        assert "Store decryption failed" in result.output
        assert "brokr is healthy" not in result.output


class TestBuildRunEnv:
    def test_secrets_override_base(self):
        base = {"PATH": "/usr/bin", "EXISTING": "old"}
        secrets = {"EXISTING": "new", "NEW_KEY": "val"}
        env = build_run_env(secrets, base_env=base)
        assert env["EXISTING"] == "new"
        assert env["NEW_KEY"] == "val"
        assert env["PATH"] == "/usr/bin"

    def test_only_filters_keys(self):
        secrets = {"A": "1", "B": "2", "C": "3"}
        env = build_run_env(secrets, base_env={}, only={"A", "C"})
        assert env["A"] == "1"
        assert env["C"] == "3"
        assert "B" not in env

    def test_only_missing_key_warns(self, capsys):
        secrets = {"A": "1"}
        env = build_run_env(secrets, base_env={}, only={"A", "MISSING"})
        assert env["A"] == "1"
        assert "MISSING" not in env
        captured = capsys.readouterr()
        assert "MISSING" in captured.err

    def test_empty_secrets(self):
        env = build_run_env({}, base_env={"PATH": "/usr/bin"})
        assert env == {"PATH": "/usr/bin"}


class TestRun:
    def test_no_command_errors(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["run"])
        assert result.exit_code == 1
        assert "no command provided" in result.output

    def test_empty_store_warns(self, tmp_path, monkeypatch):
        _init_store(tmp_path, monkeypatch)
        result = runner.invoke(app, ["run", "echo", "hello"])
        assert "no secrets to inject" in result.output

    @pytest.mark.real_kdf
    def test_secrets_injected_as_env_vars(self, tmp_path, monkeypatch):
        import subprocess
        from brokr.store import load_store, save_store

        _init_store(tmp_path, monkeypatch)
        data = load_store("secret", store_dir=tmp_path)
        data["secrets"]["TEST_KEY"] = "test_value_42"
        save_store(data, "secret", store_dir=tmp_path)

        result = subprocess.run(
            [
                sys.executable, "-m", "brokr", "run",
                sys.executable, "-c",
                "import os; print(os.environ.get('TEST_KEY', 'NOT_FOUND'))",
            ],
            capture_output=True,
            text=True,
            env={
                **os.environ,
                "BROKR_DIR": str(tmp_path),
                "BROKR_PASSPHRASE": "secret",
            },
        )
        assert "test_value_42" in result.stdout

    @pytest.mark.real_kdf
    def test_only_flag_filters(self, tmp_path, monkeypatch):
        import subprocess
        from brokr.store import load_store, save_store

        _init_store(tmp_path, monkeypatch)
        data = load_store("secret", store_dir=tmp_path)
        data["secrets"]["KEEP"] = "yes"
        data["secrets"]["SKIP"] = "no"
        save_store(data, "secret", store_dir=tmp_path)

        result = subprocess.run(
            [
                sys.executable, "-m", "brokr", "run",
                "--only", "KEEP",
                sys.executable, "-c",
                "import os; print(os.environ.get('KEEP', 'NOT_FOUND'), os.environ.get('SKIP', 'NOT_FOUND'))",
            ],
            capture_output=True,
            text=True,
            env={
                **os.environ,
                "BROKR_DIR": str(tmp_path),
                "BROKR_PASSPHRASE": "secret",
            },
        )
        assert "yes" in result.stdout
        assert "NOT_FOUND" in result.stdout

    @pytest.mark.real_kdf
    def test_exit_code_forwarded(self, tmp_path, monkeypatch):
        import subprocess
        from brokr.store import load_store, save_store

        _init_store(tmp_path, monkeypatch)

        result = subprocess.run(
            [
                sys.executable, "-m", "brokr", "run",
                sys.executable, "-c", "import sys; sys.exit(42)",
            ],
            capture_output=True,
            text=True,
            env={
                **os.environ,
                "BROKR_DIR": str(tmp_path),
                "BROKR_PASSPHRASE": "secret",
            },
        )
        assert result.returncode == 42
