import logging
import os
from collections.abc import Iterator
from contextlib import contextmanager
from typing import TYPE_CHECKING

import pytest
from fixtures_flakes import FlakeForTest
from helpers import cli

from clan_cli.errors import ClanError

if TYPE_CHECKING:
    from age_keys import KeyPair

log = logging.getLogger(__name__)


def _test_identities(
    what: str,
    test_flake: FlakeForTest,
    capsys: pytest.CaptureFixture,
    age_keys: list["KeyPair"],
) -> None:
    sops_folder = test_flake.path / "sops"

    cli.run(
        [
            "secrets",
            what,
            "add",
            "--flake",
            str(test_flake.path),
            "foo",
            age_keys[0].pubkey,
        ]
    )
    assert (sops_folder / what / "foo" / "key.json").exists()

    with pytest.raises(ClanError):  # raises "foo already exists"
        cli.run(
            [
                "secrets",
                what,
                "add",
                "--flake",
                str(test_flake.path),
                "foo",
                age_keys[0].pubkey,
            ]
        )

    # rotate the key
    cli.run(
        [
            "secrets",
            what,
            "add",
            "--flake",
            str(test_flake.path),
            "-f",
            "foo",
            age_keys[1].privkey,
        ]
    )

    capsys.readouterr()  # empty the buffer
    cli.run(
        [
            "secrets",
            what,
            "get",
            "--flake",
            str(test_flake.path),
            "foo",
        ]
    )
    out = capsys.readouterr()  # empty the buffer
    assert age_keys[1].pubkey in out.out

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
    out = capsys.readouterr()  # empty the buffer
    assert "foo" in out.out

    cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])
    assert not (sops_folder / what / "foo" / "key.json").exists()

    with pytest.raises(ClanError):  # already removed
        cli.run(["secrets", what, "remove", "--flake", str(test_flake.path), "foo"])

    capsys.readouterr()
    cli.run(["secrets", what, "list", "--flake", str(test_flake.path)])
    out = capsys.readouterr()
    assert "foo" not in out.out


def test_users(
    test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None:
    _test_identities("users", test_flake, capsys, age_keys)


def test_machines(
    test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None:
    _test_identities("machines", test_flake, capsys, age_keys)


def test_groups(
    test_flake: FlakeForTest, capsys: pytest.CaptureFixture, age_keys: list["KeyPair"]
) -> None:
    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
    assert capsys.readouterr().out == ""

    with pytest.raises(ClanError):  # machine does not exist yet
        cli.run(
            [
                "secrets",
                "groups",
                "add-machine",
                "--flake",
                str(test_flake.path),
                "group1",
                "machine1",
            ]
        )
    with pytest.raises(ClanError):  # user does not exist yet
        cli.run(
            [
                "secrets",
                "groups",
                "add-user",
                "--flake",
                str(test_flake.path),
                "groupb1",
                "user1",
            ]
        )
    cli.run(
        [
            "secrets",
            "machines",
            "add",
            "--flake",
            str(test_flake.path),
            "machine1",
            age_keys[0].pubkey,
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "add-machine",
            "--flake",
            str(test_flake.path),
            "group1",
            "machine1",
        ]
    )

    # Should this fail?
    cli.run(
        [
            "secrets",
            "groups",
            "add-machine",
            "--flake",
            str(test_flake.path),
            "group1",
            "machine1",
        ]
    )

    cli.run(
        [
            "secrets",
            "users",
            "add",
            "--flake",
            str(test_flake.path),
            "user1",
            age_keys[0].pubkey,
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "add-user",
            "--flake",
            str(test_flake.path),
            "group1",
            "user1",
        ]
    )

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "groups", "list", "--flake", str(test_flake.path)])
    out = capsys.readouterr().out
    assert "user1" in out
    assert "machine1" in out

    cli.run(
        [
            "secrets",
            "groups",
            "remove-user",
            "--flake",
            str(test_flake.path),
            "group1",
            "user1",
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "remove-machine",
            "--flake",
            str(test_flake.path),
            "group1",
            "machine1",
        ]
    )
    groups = os.listdir(test_flake.path / "sops" / "groups")
    assert len(groups) == 0


@contextmanager
def use_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
    old_key = os.environ["SOPS_AGE_KEY_FILE"]
    monkeypatch.delenv("SOPS_AGE_KEY_FILE")
    monkeypatch.setenv("SOPS_AGE_KEY", key)
    try:
        yield
    finally:
        monkeypatch.delenv("SOPS_AGE_KEY")
        monkeypatch.setenv("SOPS_AGE_KEY_FILE", old_key)


def test_secrets(
    test_flake: FlakeForTest,
    capsys: pytest.CaptureFixture,
    monkeypatch: pytest.MonkeyPatch,
    age_keys: list["KeyPair"],
) -> None:
    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "list", "--flake", str(test_flake.path)])
    assert capsys.readouterr().out == ""

    monkeypatch.setenv("SOPS_NIX_SECRET", "foo")
    monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key"))
    cli.run(["secrets", "key", "generate", "--flake", str(test_flake.path)])
    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])
    key = capsys.readouterr().out
    assert key.startswith("age1")
    cli.run(
        ["secrets", "users", "add", "--flake", str(test_flake.path), "testuser", key]
    )

    with pytest.raises(ClanError):  # does not exist yet
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "nonexisting"])
    cli.run(["secrets", "set", "--flake", str(test_flake.path), "initialkey"])
    capsys.readouterr()
    cli.run(["secrets", "get", "--flake", str(test_flake.path), "initialkey"])
    assert capsys.readouterr().out == "foo"
    capsys.readouterr()
    cli.run(["secrets", "users", "list", "--flake", str(test_flake.path)])
    users = capsys.readouterr().out.rstrip().split("\n")
    assert len(users) == 1, f"users: {users}"
    owner = users[0]

    monkeypatch.setenv("EDITOR", "cat")
    cli.run(["secrets", "set", "--edit", "--flake", str(test_flake.path), "initialkey"])
    monkeypatch.delenv("EDITOR")

    cli.run(["secrets", "rename", "--flake", str(test_flake.path), "initialkey", "key"])

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "list", "--flake", str(test_flake.path)])
    assert capsys.readouterr().out == "key\n"

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "list", "--flake", str(test_flake.path), "nonexisting"])
    assert capsys.readouterr().out == ""

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "list", "--flake", str(test_flake.path), "key"])
    assert capsys.readouterr().out == "key\n"

    cli.run(
        [
            "secrets",
            "machines",
            "add",
            "--flake",
            str(test_flake.path),
            "machine1",
            age_keys[1].pubkey,
        ]
    )
    cli.run(
        [
            "secrets",
            "machines",
            "add-secret",
            "--flake",
            str(test_flake.path),
            "machine1",
            "key",
        ]
    )
    capsys.readouterr()
    cli.run(["secrets", "machines", "list", "--flake", str(test_flake.path)])
    assert capsys.readouterr().out == "machine1\n"

    with use_key(age_keys[1].privkey, monkeypatch):
        capsys.readouterr()
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])

        assert capsys.readouterr().out == "foo"

    # rotate machines key
    cli.run(
        [
            "secrets",
            "machines",
            "add",
            "--flake",
            str(test_flake.path),
            "-f",
            "machine1",
            age_keys[0].privkey,
        ]
    )

    # should also rotate the encrypted secret
    with use_key(age_keys[0].privkey, monkeypatch):
        capsys.readouterr()
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])

        assert capsys.readouterr().out == "foo"

    cli.run(
        [
            "secrets",
            "machines",
            "remove-secret",
            "--flake",
            str(test_flake.path),
            "machine1",
            "key",
        ]
    )

    cli.run(
        [
            "secrets",
            "users",
            "add",
            "--flake",
            str(test_flake.path),
            "user1",
            age_keys[1].pubkey,
        ]
    )
    cli.run(
        [
            "secrets",
            "users",
            "add-secret",
            "--flake",
            str(test_flake.path),
            "user1",
            "key",
        ]
    )
    capsys.readouterr()
    with use_key(age_keys[1].privkey, monkeypatch):
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
    assert capsys.readouterr().out == "foo"
    cli.run(
        [
            "secrets",
            "users",
            "remove-secret",
            "--flake",
            str(test_flake.path),
            "user1",
            "key",
        ]
    )

    with pytest.raises(ClanError):  # does not exist yet
        cli.run(
            [
                "secrets",
                "groups",
                "add-secret",
                "--flake",
                str(test_flake.path),
                "admin-group",
                "key",
            ]
        )
    cli.run(
        [
            "secrets",
            "groups",
            "add-user",
            "--flake",
            str(test_flake.path),
            "admin-group",
            "user1",
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "add-user",
            "--flake",
            str(test_flake.path),
            "admin-group",
            owner,
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "add-secret",
            "--flake",
            str(test_flake.path),
            "admin-group",
            "key",
        ]
    )

    capsys.readouterr()  # empty the buffer
    cli.run(
        [
            "secrets",
            "set",
            "--flake",
            str(test_flake.path),
            "--group",
            "admin-group",
            "key2",
        ]
    )

    with use_key(age_keys[1].privkey, monkeypatch):
        capsys.readouterr()
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
        assert capsys.readouterr().out == "foo"

    # extend group will update secrets
    cli.run(
        [
            "secrets",
            "users",
            "add",
            "--flake",
            str(test_flake.path),
            "user2",
            age_keys[2].pubkey,
        ]
    )
    cli.run(
        [
            "secrets",
            "groups",
            "add-user",
            "--flake",
            str(test_flake.path),
            "admin-group",
            "user2",
        ]
    )

    with use_key(age_keys[2].privkey, monkeypatch):  # user2
        capsys.readouterr()
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
        assert capsys.readouterr().out == "foo"

    cli.run(
        [
            "secrets",
            "groups",
            "remove-user",
            "--flake",
            str(test_flake.path),
            "admin-group",
            "user2",
        ]
    )
    with pytest.raises(ClanError), use_key(age_keys[2].privkey, monkeypatch):
        # user2 is not in the group anymore
        capsys.readouterr()
        cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
        print(capsys.readouterr().out)

    cli.run(
        [
            "secrets",
            "groups",
            "remove-secret",
            "--flake",
            str(test_flake.path),
            "admin-group",
            "key",
        ]
    )

    cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key"])
    cli.run(["secrets", "remove", "--flake", str(test_flake.path), "key2"])

    capsys.readouterr()  # empty the buffer
    cli.run(["secrets", "list", "--flake", str(test_flake.path)])
    assert capsys.readouterr().out == ""
