"""Authentication for the Kredo Drift API.

Tiered auth:
  - Free tier: X-API-Key header (API key verified against bcrypt hash)
  - Paid tier: X-API-Key + X-Signature (Ed25519 over canonical JSON body)
"""

from __future__ import annotations

import hashlib
import hmac
import os
from typing import Optional

import bcrypt
from fastapi import Depends, Header, HTTPException, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from kredo_drift.server.db.models import Agent
from kredo_drift.server.db.session import get_db


def generate_api_key(length: int = 32) -> str:
    """Generate a cryptographically random API key (hex-encoded).

    Args:
        length: Number of random bytes (default 32 = 256-bit).

    Returns:
        Hex-encoded API key string (64 chars for 32 bytes).
    """
    return os.urandom(length).hex()


def hash_api_key(api_key: str) -> str:
    """Hash an API key with bcrypt for storage."""
    return bcrypt.hashpw(api_key.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def verify_api_key(api_key: str, hashed: str) -> bool:
    """Verify an API key against its bcrypt hash."""
    return bcrypt.checkpw(api_key.encode("utf-8"), hashed.encode("utf-8"))


def verify_request_signature(body: bytes, signature_header: str, pubkey: str) -> bool:
    """Verify an Ed25519 signature over the request body.

    Args:
        body: Raw request body bytes.
        signature_header: "ed25519:<hex>" signature from X-Signature header.
        pubkey: "ed25519:<hex>" public key from the agent record.

    Returns:
        True if signature is valid.
    """
    if not signature_header.startswith("ed25519:") or not pubkey.startswith("ed25519:"):
        return False

    try:
        from nacl.signing import VerifyKey
        from nacl.exceptions import BadSignatureError

        sig_bytes = bytes.fromhex(signature_header.removeprefix("ed25519:"))
        pubkey_bytes = bytes.fromhex(pubkey.removeprefix("ed25519:"))
        verify_key = VerifyKey(pubkey_bytes)
        verify_key.verify(body, sig_bytes)
        return True
    except (BadSignatureError, ValueError):
        return False


class AuthenticatedAgent:
    """Represents an authenticated agent from a request."""

    def __init__(self, agent: Agent, signature_verified: bool = False):
        self.agent = agent
        self.pubkey = agent.pubkey
        self.agent_id = agent.agent_id
        self.tier = agent.tier
        self.signature_verified = signature_verified


async def require_api_key(
    request: Request,
    x_api_key: str = Header(..., alias="X-API-Key"),
    db: AsyncSession = Depends(get_db),
) -> AuthenticatedAgent:
    """Authenticate via API key. Required for all tiers."""
    # WHY linear scan with bcrypt: bcrypt hashes are intentionally non-indexable
    # (salted), so we must check each agent. This is O(n) per request but
    # acceptable for early-stage (<1000 agents). Production will add a key
    # prefix index: store first 8 chars of the API key in plaintext for lookup,
    # then verify the full key with bcrypt only on the matched row.
    result = await db.execute(select(Agent).where(Agent.active == True))
    agents = result.scalars().all()

    for agent in agents:
        if verify_api_key(x_api_key, agent.api_key_hash):
            return AuthenticatedAgent(agent=agent)

    raise HTTPException(status_code=401, detail="Invalid API key")


async def require_signature(
    request: Request,
    auth: AuthenticatedAgent = Depends(require_api_key),
    x_signature: Optional[str] = Header(None, alias="X-Signature"),
) -> AuthenticatedAgent:
    """Authenticate via API key + Ed25519 signature. Required for paid tier operations."""
    # WHY free tier skips signature: Free tier agents don't have registered
    # Ed25519 keys yet (they only have API keys). Requiring signatures from
    # day one would block adoption. Paid tier upgrades to dual-auth (API key +
    # request signature) for non-repudiation.
    if auth.tier == "free":
        return auth

    if not x_signature:
        raise HTTPException(
            status_code=401,
            detail="X-Signature header required for paid tier",
        )

    body = await request.body()
    if not verify_request_signature(body, x_signature, auth.pubkey):
        raise HTTPException(status_code=401, detail="Invalid request signature")

    auth.signature_verified = True
    return auth
