"""Agent and human registration endpoints.

POST /v1/auth/register       — Human registration (email)
POST /v1/register            — Agent registration (returns challenge)
POST /v1/register/complete   — Complete with Ed25519 challenge-response
POST /v1/register/link-owner — Link agent to human owner
"""

from __future__ import annotations

import os
import hashlib
from datetime import datetime, timezone
from uuid import uuid4

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, EmailStr
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from kredo_drift.server.auth import generate_api_key, hash_api_key, verify_request_signature
from kredo_drift.server.db.models import Agent, AuditEvent
from kredo_drift.server.db.session import get_db
from kredo_drift.server.deps import get_service_signing_key, get_service_pubkey
from kredo_drift.canonical import canonical_json

router = APIRouter(tags=["registration"])

# WHY in-memory challenge store: Challenges are ephemeral (valid for seconds,
# not days). Redis adds operational complexity for a v0.2 launch. The tradeoff
# is that challenges are lost on server restart, which is acceptable -- the
# agent simply retries registration. Production will move to Redis with TTL
# expiry to support multi-instance deployments.
_pending_challenges: dict[str, dict] = {}


class HumanRegisterRequest(BaseModel):
    email: str  # EmailStr requires email-validator; keep simple for now
    name: str


class HumanRegisterResponse(BaseModel):
    owner_pubkey: str
    message: str


class AgentRegisterRequest(BaseModel):
    agent_id: str
    name: str
    pubkey: str  # "ed25519:<hex>"


class AgentRegisterResponse(BaseModel):
    challenge: str
    challenge_id: str


class AgentCompleteRequest(BaseModel):
    challenge_id: str
    signature: str  # "ed25519:<hex>" — signature over the challenge


class AgentCompleteResponse(BaseModel):
    agent_id: str
    api_key: str  # returned once, never again
    service_pubkey: str


class LinkOwnerRequest(BaseModel):
    agent_pubkey: str
    owner_pubkey: str
    signature: str  # agent signs the link request


@router.post("/auth/register")
async def register_human(
    req: HumanRegisterRequest,
    db: AsyncSession = Depends(get_db),
) -> HumanRegisterResponse:
    """Register a human owner. Returns an owner pubkey for linking agents."""
    from nacl.signing import SigningKey as NaClSigningKey

    # Generate owner keypair (in production, human would provide their own)
    sk = NaClSigningKey.generate()
    owner_pubkey = f"ed25519:{sk.verify_key.encode().hex()}"

    db.add(AuditEvent(
        event_type="human_register",
        detail={"email": req.email, "name": req.name, "owner_pubkey": owner_pubkey},
    ))

    return HumanRegisterResponse(
        owner_pubkey=owner_pubkey,
        message="Owner registered. Use this pubkey to link agents.",
    )


@router.post("/register")
async def register_agent(
    req: AgentRegisterRequest,
    db: AsyncSession = Depends(get_db),
) -> AgentRegisterResponse:
    """Start agent registration. Returns a challenge to prove key ownership."""
    # Check for duplicate agent_id
    existing = await db.execute(
        select(Agent).where(Agent.agent_id == req.agent_id)
    )
    if existing.scalar_one_or_none():
        raise HTTPException(status_code=409, detail=f"Agent '{req.agent_id}' already registered")

    # Validate pubkey format
    # WHY 72 chars: "ed25519:" prefix (8 chars) + 32-byte key as hex (64 chars).
    # Rejecting malformed keys early prevents cryptographic errors downstream
    # and ensures the pubkey can be used directly as a DB primary key.
    if not req.pubkey.startswith("ed25519:") or len(req.pubkey) != 72:
        raise HTTPException(status_code=400, detail="Invalid pubkey format. Expected 'ed25519:<64 hex chars>'")

    # Generate challenge
    challenge = os.urandom(32).hex()
    challenge_id = uuid4().hex

    _pending_challenges[challenge_id] = {
        "challenge": challenge,
        "agent_id": req.agent_id,
        "name": req.name,
        "pubkey": req.pubkey,
        "created_at": datetime.now(timezone.utc),
    }

    return AgentRegisterResponse(
        challenge=challenge,
        challenge_id=challenge_id,
    )


@router.post("/register/complete")
async def complete_registration(
    req: AgentCompleteRequest,
    db: AsyncSession = Depends(get_db),
) -> AgentCompleteResponse:
    """Complete agent registration by proving Ed25519 key ownership."""
    pending = _pending_challenges.pop(req.challenge_id, None)
    if not pending:
        raise HTTPException(status_code=400, detail="Invalid or expired challenge")

    # Verify the signature over the challenge
    challenge_bytes = pending["challenge"].encode("utf-8")
    if not verify_request_signature(challenge_bytes, req.signature, pending["pubkey"]):
        raise HTTPException(status_code=401, detail="Challenge signature verification failed")

    # Generate API key
    api_key = generate_api_key()

    # Create agent record
    agent = Agent(
        pubkey=pending["pubkey"],
        agent_id=pending["agent_id"],
        name=pending["name"],
        api_key_hash=hash_api_key(api_key),
    )
    db.add(agent)

    db.add(AuditEvent(
        event_type="agent_register",
        agent_pubkey=pending["pubkey"],
        detail={"agent_id": pending["agent_id"]},
    ))

    service_pubkey = get_service_pubkey()

    return AgentCompleteResponse(
        agent_id=pending["agent_id"],
        api_key=api_key,
        service_pubkey=service_pubkey,
    )


@router.post("/register/link-owner")
async def link_owner(
    req: LinkOwnerRequest,
    db: AsyncSession = Depends(get_db),
):
    """Link an agent to a human owner."""
    result = await db.execute(
        select(Agent).where(Agent.pubkey == req.agent_pubkey)
    )
    agent = result.scalar_one_or_none()
    if not agent:
        raise HTTPException(status_code=404, detail="Agent not found")

    # Verify the agent signed this link request
    link_payload = canonical_json({"agent_pubkey": req.agent_pubkey, "owner_pubkey": req.owner_pubkey})
    if not verify_request_signature(link_payload, req.signature, req.agent_pubkey):
        raise HTTPException(status_code=401, detail="Invalid signature")

    agent.owner_pubkey = req.owner_pubkey

    db.add(AuditEvent(
        event_type="link_owner",
        agent_pubkey=req.agent_pubkey,
        detail={"owner_pubkey": req.owner_pubkey},
    ))

    return {"status": "linked", "agent_id": agent.agent_id, "owner_pubkey": req.owner_pubkey}
