"""Service account loading and decoding."""

from __future__ import annotations

import base64
import json
import os
import shutil
import subprocess
import sys
from dataclasses import dataclass
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

if TYPE_CHECKING:
    pass


class ServiceAccountJSON(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str
    pgp_key: str


@dataclass(slots=True)
class ServiceAccount:
    name: str
    private_key: Any

    @property
    def fingerprint(self) -> str:
        return str(getattr(self.private_key, "fingerprint")).lower()


class GPGCLIPrivateKey:
    """Detached signer backed by local gpg CLI."""

    def __init__(self, *, homedir: str, fingerprint: str, keeper: TemporaryDirectory[str]) -> None:
        self.homedir = homedir
        self.fingerprint = fingerprint
        self._keeper = keeper

    @classmethod
    def from_armored(cls, armored_key: str) -> GPGCLIPrivateKey:
        gpg_bin = shutil.which("gpg")
        if not gpg_bin:
            raise ValueError("failed to parse service-account key with pgpy and gpg is not available")

        keeper = TemporaryDirectory(prefix="omnictl-gpg-")
        homedir = keeper.name

        subprocess.run(
            [gpg_bin, "--homedir", homedir, "--batch", "--yes", "--import"],
            input=armored_key.encode("utf-8"),
            capture_output=True,
            check=True,
        )

        proc = subprocess.run(
            [gpg_bin, "--homedir", homedir, "--batch", "--with-colons", "--list-secret-keys"],
            capture_output=True,
            check=True,
            text=True,
        )

        fingerprint = ""
        for line in proc.stdout.splitlines():
            if line.startswith("fpr:"):
                fields = line.split(":")
                if len(fields) > 9 and fields[9]:
                    fingerprint = fields[9]
                    break

        if not fingerprint:
            raise ValueError("failed to derive fingerprint from imported service-account key")

        return cls(homedir=homedir, fingerprint=fingerprint, keeper=keeper)

    def sign_detached(self, payload: bytes) -> bytes:
        gpg_bin = shutil.which("gpg")
        if not gpg_bin:
            raise ValueError("gpg binary not found")

        proc = subprocess.run(
            [
                gpg_bin,
                "--homedir",
                self.homedir,
                "--batch",
                "--yes",
                "--pinentry-mode",
                "loopback",
                "--local-user",
                self.fingerprint,
                "--detach-sign",
                "--output",
                "-",
            ],
            input=payload,
            capture_output=True,
            check=True,
        )
        return proc.stdout


DEFAULT_SERVICE_ACCOUNT_ENV_ALIASES = ["OMNI_SERVICE_ACCOUNT_KEY", "SIDERO_SERVICE_ACCOUNT_KEY"]


def _patch_pgpy_unknown_hash_algorithm() -> None:
    from pgpy.constants import HashAlgorithm

    if getattr(HashAlgorithm, "__omnictl_patched__", False):
        return

    hash_algorithm_any: Any = HashAlgorithm
    original_missing = hash_algorithm_any._missing_

    def _missing_(cls: Any, value: Any) -> Any:
        if isinstance(value, int):
            fallback = getattr(cls, "SHA512", None) or getattr(cls, "SHA256", None)
            if fallback is not None:
                return fallback
        return original_missing(value)

    hash_algorithm_any._missing_ = classmethod(_missing_)
    setattr(HashAlgorithm, "__omnictl_patched__", True)


def decode_service_account(value_base64: str) -> ServiceAccount:
    if "imghdr" not in sys.modules:
        try:
            __import__("imghdr")
        except ModuleNotFoundError:
            from omni._compat import imghdr as compat_imghdr

            sys.modules["imghdr"] = compat_imghdr

    from pgpy import PGPKey

    raw = base64.b64decode(value_base64.encode("utf-8"), validate=True)
    payload = json.loads(raw.decode("utf-8"))
    parsed = ServiceAccountJSON.model_validate(payload)

    try:
        key, _ = PGPKey.from_blob(parsed.pgp_key)
        if key is not None:
            return ServiceAccount(name=parsed.name, private_key=key)
    except Exception as exc:
        if "HashAlgorithm" in str(exc):
            _patch_pgpy_unknown_hash_algorithm()
            key, _ = PGPKey.from_blob(parsed.pgp_key)
            if key is not None:
                return ServiceAccount(name=parsed.name, private_key=key)

    # Fallback for keys that pgpy can't parse (for example newer hash algorithms).
    gpg_key = GPGCLIPrivateKey.from_armored(parsed.pgp_key)
    return ServiceAccount(name=parsed.name, private_key=gpg_key)


def load_service_account_from_env(aliases: list[str] | None = None) -> ServiceAccount | None:
    env_aliases = aliases or DEFAULT_SERVICE_ACCOUNT_ENV_ALIASES
    for alias in env_aliases:
        value = os.getenv(alias)
        if value:
            return decode_service_account(value)
    return None
