from __future__ import annotations

import subprocess
from unittest.mock import patch

import pytest

from k1link.device_plugins.xgrids_k1.protocol.application_authority import (
    KEYCHAIN_ACCOUNT,
    KEYCHAIN_SERVICE,
    ApplicationAuthorityLoadError,
    MacOSKeychainApplicationAuthorityLoader,
    MacOSKeychainApplicationAuthorityProvisioner,
)

PRIVATE_AUTHORITY = b"11111111-2222-3333-4444-555555555555\n"


@patch("platform.system", return_value="Darwin")
def test_runtime_authority_loads_through_security_framework_without_subprocess(
    _system: object,
) -> None:
    calls: list[tuple[str, str]] = []

    def framework_reader(*, service: str, account: str) -> bytes:
        calls.append((service, account))
        return PRIVATE_AUTHORITY

    loader = MacOSKeychainApplicationAuthorityLoader(framework_reader=framework_reader)
    authority = loader.load()

    assert calls == [(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT)]
    assert PRIVATE_AUTHORITY.decode().strip() not in repr(authority)


@patch("platform.system", return_value="Darwin")
@patch("shutil.which", return_value="/usr/bin/security")
def test_authority_loads_only_from_fixed_macos_keychain_item(
    _which: object,
    _system: object,
) -> None:
    calls: list[list[str]] = []

    def runner(
        args: list[str],
        *,
        capture_output: bool,
        check: bool,
        timeout: float,
    ) -> subprocess.CompletedProcess[bytes]:
        calls.append(args)
        assert capture_output
        assert check is False
        assert timeout == 5.0
        return subprocess.CompletedProcess(args, 0, stdout=PRIVATE_AUTHORITY, stderr=b"")

    loader = MacOSKeychainApplicationAuthorityLoader(runner=runner)
    authority = loader.load()

    assert calls == [
        [
            "/usr/bin/security",
            "find-generic-password",
            "-s",
            KEYCHAIN_SERVICE,
            "-a",
            KEYCHAIN_ACCOUNT,
            "-w",
        ]
    ]
    assert PRIVATE_AUTHORITY.decode().strip() not in repr(authority)
    assert loader.snapshot().as_dict() == {
        "provider": "macos-keychain",
        "service": KEYCHAIN_SERVICE,
        "account": KEYCHAIN_ACCOUNT,
        "secret_cached": False,
        "secret_exportable": False,
        "file_fallback_enabled": False,
        "environment_fallback_enabled": False,
    }


@patch("platform.system", return_value="Linux")
def test_authority_loader_has_no_non_keychain_fallback(_system: object) -> None:
    loader = MacOSKeychainApplicationAuthorityLoader()

    with pytest.raises(ApplicationAuthorityLoadError, match="only through macOS Keychain"):
        loader.load()


@patch("platform.system", return_value="Darwin")
@patch("shutil.which", return_value="/usr/bin/security")
def test_authority_loader_redacts_keychain_failures(
    _which: object,
    _system: object,
) -> None:
    private_error = b"private-keychain-diagnostic"

    def runner(
        args: list[str],
        **_kwargs: object,
    ) -> subprocess.CompletedProcess[bytes]:
        return subprocess.CompletedProcess(args, 44, stdout=b"", stderr=private_error)

    with pytest.raises(ApplicationAuthorityLoadError) as error:
        MacOSKeychainApplicationAuthorityLoader(runner=runner).load()

    assert private_error.decode() not in str(error.value)


@patch("platform.system", return_value="Darwin")
@patch("shutil.which", return_value="/usr/bin/security")
def test_authority_loader_rejects_wrong_profile_length_without_reflection(
    _which: object,
    _system: object,
) -> None:
    invalid_secret = b"too-short-private-value"

    def runner(
        args: list[str],
        **_kwargs: object,
    ) -> subprocess.CompletedProcess[bytes]:
        return subprocess.CompletedProcess(args, 0, stdout=invalid_secret, stderr=b"")

    with pytest.raises(ApplicationAuthorityLoadError) as error:
        MacOSKeychainApplicationAuthorityLoader(runner=runner).load()

    assert invalid_secret.decode() not in str(error.value)


@patch("platform.system", return_value="Darwin")
def test_framework_authority_failure_is_redacted(_system: object) -> None:
    private_error = "private-framework-diagnostic"

    def framework_reader(*, service: str, account: str) -> bytes:
        assert service == KEYCHAIN_SERVICE
        assert account == KEYCHAIN_ACCOUNT
        raise RuntimeError(private_error)

    with pytest.raises(ApplicationAuthorityLoadError) as error:
        MacOSKeychainApplicationAuthorityLoader(framework_reader=framework_reader).load()

    assert private_error not in str(error.value)


@patch("sys.stdout.isatty", return_value=True)
@patch("sys.stdin.isatty", return_value=True)
@patch("platform.system", return_value="Darwin")
@patch("shutil.which", return_value="/usr/bin/security")
def test_authority_provisioning_uses_hidden_security_prompt_and_validates_item(
    _which: object,
    _system: object,
    _stdin_tty: object,
    _stdout_tty: object,
) -> None:
    provisioning_calls: list[list[str]] = []

    def interactive_runner(
        args: list[str],
        *,
        check: bool,
        timeout: float,
    ) -> subprocess.CompletedProcess[bytes]:
        provisioning_calls.append(args)
        assert check is False
        assert timeout == 300.0
        return subprocess.CompletedProcess(args, 0)

    def lookup_runner(
        args: list[str],
        **_kwargs: object,
    ) -> subprocess.CompletedProcess[bytes]:
        return subprocess.CompletedProcess(args, 0, stdout=PRIVATE_AUTHORITY, stderr=b"")

    provisioner = MacOSKeychainApplicationAuthorityProvisioner(
        runner=interactive_runner,
        loader=MacOSKeychainApplicationAuthorityLoader(runner=lookup_runner),
    )
    snapshot = provisioner.provision_interactively()

    assert provisioning_calls == [
        [
            "/usr/bin/security",
            "add-generic-password",
            "-U",
            "-s",
            KEYCHAIN_SERVICE,
            "-a",
            KEYCHAIN_ACCOUNT,
            "-w",
        ]
    ]
    assert PRIVATE_AUTHORITY.decode().strip() not in str(provisioning_calls)
    assert snapshot.secret_cached is False
    assert snapshot.secret_exportable is False


@patch("sys.stdout.isatty", return_value=False)
@patch("sys.stdin.isatty", return_value=False)
@patch("platform.system", return_value="Darwin")
def test_authority_provisioning_requires_an_interactive_terminal(
    _system: object,
    _stdin_tty: object,
    _stdout_tty: object,
) -> None:
    with pytest.raises(ApplicationAuthorityLoadError, match="interactive terminal"):
        MacOSKeychainApplicationAuthorityProvisioner().provision_interactively()
