import hashlib
import secrets

from django.conf import settings
from django.db import models
from django.utils import timezone


def hash_token(plaintext: str) -> str:
    """SHA-256 hex digest of an MCP bearer token. 64 chars."""
    return hashlib.sha256(plaintext.encode("utf-8")).hexdigest()


class MCPTokenManager(models.Manager):
    def create_token(self, *, user, name, allowed_tools=None, expires_at=None):
        """Generate a new bearer token, store its hash, and return (instance, plaintext).

        The plaintext is returned exactly once and is never persisted. Callers
        must surface it to the human and rely on the user to copy it; after
        this method returns, the plaintext is unrecoverable from the database.
        """
        plaintext = secrets.token_urlsafe(48)
        instance = self.create(
            user=user,
            name=name,
            token_hash=hash_token(plaintext),
            allowed_tools=list(allowed_tools or []),
            expires_at=expires_at,
        )
        return instance, plaintext


class MCPToken(models.Model):
    """Bearer token for authenticating MCP tool calls.

    Tokens are hashed at rest (SHA-256, 64-char hex). Plaintext exists only in
    memory at creation time, on the wire to the client, and in the user's own
    storage. A leaked database backup discloses no usable credentials.
    """

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="mcp_tokens",
    )
    token_hash = models.CharField(max_length=64, unique=True, db_index=True)
    name = models.CharField(max_length=100)
    is_active = models.BooleanField(default=True)
    expires_at = models.DateTimeField(null=True, blank=True)
    last_used_at = models.DateTimeField(null=True, blank=True)
    allowed_tools = models.JSONField(default=list, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = MCPTokenManager()

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.name} ({self.user})"

    @property
    def is_valid(self) -> bool:
        if not self.is_active:
            return False
        if self.expires_at and self.expires_at < timezone.now():
            return False
        return True

    def can_use_tool(self, tool_name: str) -> bool:
        if not self.allowed_tools:
            return True
        return tool_name in self.allowed_tools

    def record_usage(self):
        self.last_used_at = timezone.now()
        self.save(update_fields=["last_used_at"])

    def get_masked_token(self) -> str:
        """Token-id-style display for admin and dashboard.

        Plaintext is unrecoverable, so we display the first 8 chars of the
        hash prefixed with `mcp_…`. Stable per token, never reveals plaintext.
        """
        return f"mcp_…{self.token_hash[:8]}"


class MCPSigningKeyManager(models.Manager):
    def active(self):
        """Active keys, newest first. Multiple may overlap during rotation."""
        return self.filter(is_active=True).order_by("-created_at")

    def by_kid(self, kid: str):
        return self.filter(kid=kid).first()


class MCPSigningKey(models.Model):
    """HMAC signing key for per-turn JWTs minted by Daedalus.

    Per-turn tokens carry workspace + library claims and expire in minutes.
    They are validated entirely off the signature + claims; no row is stored
    per token. Only the *signing key* is persisted here, indexed by ``kid``.

    Rotation: seed a new active key, distribute the secret to Daedalus,
    flip the old one ``is_active=False``. In-flight tokens with the retired
    ``kid`` fail at ``exp`` (bounded by the per-turn TTL).
    """

    kid = models.CharField(max_length=64, unique=True, db_index=True)
    secret_hex = models.CharField(max_length=128)  # 256-bit secret = 64 hex
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    retired_at = models.DateTimeField(null=True, blank=True)
    note = models.TextField(blank=True)

    objects = MCPSigningKeyManager()

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        suffix = "active" if self.is_active else "retired"
        return f"{self.kid} ({suffix})"

    def retire(self):
        self.is_active = False
        self.retired_at = timezone.now()
        self.save(update_fields=["is_active", "retired_at"])
