import json
import subprocess
import tempfile
from datetime import datetime, timezone
from pathlib import Path

from nebula_cert_manager.exceptions import CertManagerError
from nebula_cert_manager.models import ClientInfo


class PKIError(CertManagerError):
    pass


class PKI:
    def __init__(self, nebula_cert_bin: str = "nebula-cert"):
        self.nebula_cert_bin = nebula_cert_bin

    def create_ca(self, name: str, duration: str | None = None) -> tuple[str, str]:
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = Path(tmpdir)
            cmd = [
                self.nebula_cert_bin,
                "ca",
                "-name",
                name,
                "-out-crt",
                str(tmp / "ca.crt"),
                "-out-key",
                str(tmp / "ca.key"),
            ]
            if duration:
                cmd.extend(["-duration", duration])
            self._run(cmd)
            cert_pem = (tmp / "ca.crt").read_text()
            key_pem = (tmp / "ca.key").read_text()
        return cert_pem, key_pem

    def sign(
        self,
        ca_cert: str,
        ca_key: str,
        name: str,
        ip: str,
        groups: list[str],
        duration: str | None = None,
    ) -> tuple[str, str]:
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = Path(tmpdir)
            (tmp / "ca.crt").write_text(ca_cert)
            (tmp / "ca.key").write_text(ca_key)
            cmd = [
                self.nebula_cert_bin,
                "sign",
                "-ca-crt",
                str(tmp / "ca.crt"),
                "-ca-key",
                str(tmp / "ca.key"),
                "-name",
                name,
                "-ip",
                ip,
                "-out-crt",
                str(tmp / "client.crt"),
                "-out-key",
                str(tmp / "client.key"),
            ]
            if groups:
                cmd.extend(["-groups", ",".join(groups)])
            if duration:
                cmd.extend(["-duration", duration])
            self._run(cmd)
            cert_pem = (tmp / "client.crt").read_text()
            key_pem = (tmp / "client.key").read_text()
        return cert_pem, key_pem

    def print_cert(self, cert_pem: str) -> dict:
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = Path(tmpdir)
            cert_path = tmp / "cert.crt"
            cert_path.write_text(cert_pem)
            result = self._run(
                [self.nebula_cert_bin, "print", "-json", "-path", str(cert_path)]
            )
        data = json.loads(result.stdout)
        if isinstance(data, list):
            return data[0]
        return data

    def verify(self, ca_cert: str, cert_pem: str) -> bool:
        with tempfile.TemporaryDirectory() as tmpdir:
            tmp = Path(tmpdir)
            (tmp / "ca.crt").write_text(ca_cert)
            (tmp / "cert.crt").write_text(cert_pem)
            try:
                self._run(
                    [
                        self.nebula_cert_bin,
                        "verify",
                        "-ca",
                        str(tmp / "ca.crt"),
                        "-crt",
                        str(tmp / "cert.crt"),
                    ]
                )
                return True
            except PKIError:
                return False

    def sign_and_build_client_info(
        self,
        ca_cert: str,
        ca_key: str,
        name: str,
        ip: str,
        groups: list[str],
        duration: str | None = None,
    ) -> ClientInfo:
        """Sign a certificate and return a populated ClientInfo."""
        cert_pem, key_pem = self.sign(
            ca_cert=ca_cert,
            ca_key=ca_key,
            name=name,
            ip=ip,
            groups=groups,
            duration=duration,
        )

        cert_info = self.print_cert(cert_pem)
        fingerprint = cert_info.get("fingerprint", "")
        not_after = cert_info.get("details", {}).get("notAfter", "")

        now = datetime.now(timezone.utc)
        if not_after:
            expires_at = datetime.fromisoformat(not_after.replace("Z", "+00:00"))
        else:
            expires_at = now

        return ClientInfo(
            fingerprint=fingerprint,
            cert=cert_pem,
            key=key_pem,
            issued_at=now,
            expires_at=expires_at,
        )

    def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
        try:
            return subprocess.run(cmd, capture_output=True, text=True, check=True)
        except subprocess.CalledProcessError as e:
            raise PKIError(f"nebula-cert failed: {e.stderr.strip()}") from e
        except FileNotFoundError as e:
            raise PKIError(
                f"nebula-cert binary not found: {self.nebula_cert_bin}"
            ) from e
