from __future__ import annotations

import importlib
import platform
import shutil
import subprocess
import sys
from dataclasses import dataclass
from typing import Any, Protocol, cast

from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
    ApplicationBootstrapError,
    ApplicationControlAuthority,
)

KEYCHAIN_SERVICE = "NODEDC Mission Core XGRIDS K1 OpenAPI"
KEYCHAIN_ACCOUNT = "lixelgo-application-fw-3.0.2"
KEYCHAIN_TIMEOUT_SECONDS = 5.0
KEYCHAIN_INTERACTIVE_TIMEOUT_SECONDS = 300.0


class ApplicationAuthorityLoadError(RuntimeError):
    """The private application authority could not be loaded safely."""


class CommandRunner(Protocol):
    def __call__(
        self,
        args: list[str],
        *,
        capture_output: bool,
        check: bool,
        timeout: float,
    ) -> subprocess.CompletedProcess[bytes]: ...


class KeychainSecretReader(Protocol):
    def __call__(self, *, service: str, account: str) -> bytes: ...


class InteractiveCommandRunner(Protocol):
    def __call__(
        self,
        args: list[str],
        *,
        check: bool,
        timeout: float,
    ) -> subprocess.CompletedProcess[bytes]: ...


def _read_keychain_secret_via_security_framework(*, service: str, account: str) -> bytes:
    """Read one fixed generic-password item without spawning a UI/CLI process."""

    try:
        objc = importlib.import_module("objc")
        foundation = importlib.import_module("Foundation")
        bundle = foundation.NSBundle.bundleWithPath_(
            "/System/Library/Frameworks/Security.framework"
        )
        if bundle is None or not bundle.load():
            raise RuntimeError("Security.framework is unavailable")

        symbols: dict[str, Any] = {}
        objc.loadBundleVariables(
            bundle,
            symbols,
            [
                ("kSecClass", b"@"),
                ("kSecClassGenericPassword", b"@"),
                ("kSecAttrService", b"@"),
                ("kSecAttrAccount", b"@"),
                ("kSecReturnData", b"@"),
                ("kSecMatchLimit", b"@"),
                ("kSecMatchLimitOne", b"@"),
            ],
        )
        functions: dict[str, Any] = {}
        objc.loadBundleFunctions(
            bundle,
            functions,
            [("SecItemCopyMatching", b"i@o^@")],
        )
        query = {
            symbols["kSecClass"]: symbols["kSecClassGenericPassword"],
            symbols["kSecAttrService"]: service,
            symbols["kSecAttrAccount"]: account,
            symbols["kSecReturnData"]: True,
            symbols["kSecMatchLimit"]: symbols["kSecMatchLimitOne"],
        }
        copy_matching = cast(Any, functions["SecItemCopyMatching"])
        result = copy_matching(query, None)
    except ApplicationAuthorityLoadError:
        raise
    except Exception as exc:
        raise ApplicationAuthorityLoadError("macOS Keychain authority lookup failed") from exc

    if not isinstance(result, tuple) or len(result) != 2:
        raise ApplicationAuthorityLoadError("macOS Keychain authority lookup failed")
    status, secret_data = result
    if int(status) != 0 or secret_data is None:
        raise ApplicationAuthorityLoadError("macOS Keychain authority is unavailable")
    try:
        return bytes(secret_data)
    except Exception as exc:
        raise ApplicationAuthorityLoadError("macOS Keychain authority lookup failed") from exc


@dataclass(frozen=True, slots=True)
class ApplicationAuthoritySourceSnapshot:
    provider: str = "macos-keychain"
    service: str = KEYCHAIN_SERVICE
    account: str = KEYCHAIN_ACCOUNT
    secret_cached: bool = False
    secret_exportable: bool = False
    file_fallback_enabled: bool = False
    environment_fallback_enabled: bool = False

    def as_dict(self) -> dict[str, object]:
        return {
            "provider": self.provider,
            "service": self.service,
            "account": self.account,
            "secret_cached": self.secret_cached,
            "secret_exportable": self.secret_exportable,
            "file_fallback_enabled": self.file_fallback_enabled,
            "environment_fallback_enabled": self.environment_fallback_enabled,
        }


class MacOSKeychainApplicationAuthorityLoader:
    """Read the exact-profile authority from the current user's Keychain.

    There is deliberately no environment, plaintext-file, browser or API
    fallback. Runtime reads call Apple's Security.framework in-process and
    never launch Keychain Access or the ``security`` CLI. The optional command
    runner exists only as a compatibility seam for the reviewed test harness.
    """

    def __init__(
        self,
        *,
        runner: CommandRunner | None = None,
        framework_reader: KeychainSecretReader = _read_keychain_secret_via_security_framework,
    ) -> None:
        self._runner = runner
        self._framework_reader = framework_reader

    def snapshot(self) -> ApplicationAuthoritySourceSnapshot:
        return ApplicationAuthoritySourceSnapshot()

    def load(self) -> ApplicationControlAuthority:
        if platform.system() != "Darwin":
            raise ApplicationAuthorityLoadError(
                "application authority loading is supported only through macOS Keychain"
            )
        if self._runner is None:
            try:
                secret_bytes = self._framework_reader(
                    service=KEYCHAIN_SERVICE,
                    account=KEYCHAIN_ACCOUNT,
                )
            except ApplicationAuthorityLoadError:
                raise
            except Exception as exc:
                raise ApplicationAuthorityLoadError(
                    "macOS Keychain authority lookup failed"
                ) from exc
        else:
            security = shutil.which("security")
            if security != "/usr/bin/security":
                raise ApplicationAuthorityLoadError(
                    "trusted macOS security binary is unavailable"
                )
            try:
                completed = self._runner(
                    [
                        security,
                        "find-generic-password",
                        "-s",
                        KEYCHAIN_SERVICE,
                        "-a",
                        KEYCHAIN_ACCOUNT,
                        "-w",
                    ],
                    capture_output=True,
                    check=False,
                    timeout=KEYCHAIN_TIMEOUT_SECONDS,
                )
            except (OSError, subprocess.SubprocessError) as exc:
                raise ApplicationAuthorityLoadError(
                    "macOS Keychain authority lookup failed"
                ) from exc
            if completed.returncode != 0:
                raise ApplicationAuthorityLoadError("macOS Keychain authority is unavailable")
            secret_bytes = completed.stdout

        secret_buffer = bytearray(secret_bytes)
        try:
            while secret_buffer.endswith((b"\n", b"\r")):
                secret_buffer.pop()
            try:
                secret = secret_buffer.decode("ascii")
            except UnicodeDecodeError as exc:
                raise ApplicationAuthorityLoadError(
                    "macOS Keychain authority has an invalid encoding"
                ) from exc
            try:
                return ApplicationControlAuthority(openapi_key=secret)
            except ApplicationBootstrapError as exc:
                raise ApplicationAuthorityLoadError(
                    "macOS Keychain authority does not match the reviewed profile"
                ) from exc
        finally:
            for index in range(len(secret_buffer)):
                secret_buffer[index] = 0


class MacOSKeychainApplicationAuthorityProvisioner:
    """Create or replace the fixed item through Apple's own hidden prompt.

    ``security add-generic-password`` receives ``-w`` as its final option and
    prompts on the controlling TTY. The authority therefore never enters a
    Python string, subprocess argv, environment variable, file, log or browser
    request. Provisioning is an explicit one-time operator/admin action and is
    intentionally unavailable without an interactive terminal.
    """

    def __init__(
        self,
        *,
        runner: InteractiveCommandRunner = subprocess.run,
        loader: MacOSKeychainApplicationAuthorityLoader | None = None,
    ) -> None:
        self._runner = runner
        self._loader = loader or MacOSKeychainApplicationAuthorityLoader()

    def provision_interactively(self) -> ApplicationAuthoritySourceSnapshot:
        if platform.system() != "Darwin":
            raise ApplicationAuthorityLoadError(
                "application authority provisioning is supported only through macOS Keychain"
            )
        if not sys.stdin.isatty() or not sys.stdout.isatty():
            raise ApplicationAuthorityLoadError(
                "application authority provisioning requires an interactive terminal"
            )
        security = shutil.which("security")
        if security != "/usr/bin/security":
            raise ApplicationAuthorityLoadError("trusted macOS security binary is unavailable")

        try:
            completed = self._runner(
                [
                    security,
                    "add-generic-password",
                    "-U",
                    "-s",
                    KEYCHAIN_SERVICE,
                    "-a",
                    KEYCHAIN_ACCOUNT,
                    "-w",
                ],
                check=False,
                timeout=KEYCHAIN_INTERACTIVE_TIMEOUT_SECONDS,
            )
        except (OSError, subprocess.SubprocessError) as exc:
            raise ApplicationAuthorityLoadError(
                "macOS Keychain authority provisioning failed"
            ) from exc
        if completed.returncode != 0:
            raise ApplicationAuthorityLoadError("macOS Keychain authority was not provisioned")

        # Re-open through the production loader so a wrong value fails before
        # any Mission Core control lease can be armed.
        self._loader.load()
        return self._loader.snapshot()
