"""
api.middleware.auth — API Key authentication middleware.

Security: Only SHA-256 hash stored. Key shown once at creation.
Format: ap_xxxxxxxxxxxxxxxx (32 char random, prefix ap_)
"""
from __future__ import annotations
import hashlib
import time
from typing import Optional

from agentpaas.db.session import get_db
from agentpaas.db.models import now_utc


class AuthError(Exception):
    def __init__(self, message: str, code: int = 401):
        self.message = message
        self.code = code
        super().__init__(message)


class TenantContext:
    """Injected into request state after authentication."""
    def __init__(self, tenant_id: str, tenant_name: str, plan: str,
                 user_id: str = "", role: str = "developer",
                 scopes: list = None, rate_limit: int = 60):
        self.tenant_id = tenant_id
        self.tenant_name = tenant_name
        self.plan = plan
        self.user_id = user_id
        self.role = role
        self.scopes = scopes or []
        self.rate_limit = rate_limit


def _legacy_hash_key(key: str) -> str:
    """Legacy SHA-256 hash (for migration)."""
    return hashlib.sha256(key.encode()).hexdigest()


def hash_key(key: str) -> str:
    """FIX-08: PBKDF2 hash with random salt for API keys.

    New format: "pbkdf2:<hex_salt>:<hex_hash>" — random salt per key.
    Legacy format: "<hex_hash>" (64 chars) — fixed salt, kept for backward compat.

    Old fixed-salt approach was insecure: salt derived from key prefix is
    predictable, enabling precomputation attacks.
    """
    import secrets as _secrets
    salt = _secrets.token_bytes(16)  # 128-bit random salt
    dk = hashlib.pbkdf2_hmac('sha256', key.encode(), salt, 100000)
    return f"pbkdf2:{salt.hex()}:{dk.hex()}"


def _hash_key_legacy(key: str) -> str:
    """Legacy PBKDF2 with fixed salt (for verifying old keys during migration)."""
    salt = hashlib.sha256(key[:8].encode()).digest()[:16]
    dk = hashlib.pbkdf2_hmac('sha256', key.encode(), salt, 100000)
    return dk.hex()


def verify_key_hash(key: str, stored_hash: str) -> bool:
    """Verify a key against a stored hash. Supports both new and legacy formats."""
    if stored_hash.startswith("pbkdf2:"):
        # New format: "pbkdf2:<salt_hex>:<hash_hex>"
        parts = stored_hash.split(":")
        if len(parts) != 3:
            return False
        salt = bytes.fromhex(parts[1])
        expected = parts[2]
        dk = hashlib.pbkdf2_hmac('sha256', key.encode(), salt, 100000)
        return dk.hex() == expected
    else:
        # Legacy format: fixed-salt hash (64 hex chars)
        return _hash_key_legacy(key) == stored_hash


async def authenticate(api_key: str) -> TenantContext:
    """
    Authenticate via API Key.
    Returns TenantContext with tenant info and permissions.
    """
    if not api_key:
        raise AuthError("Missing API key", 401)

    # Strip "Bearer " prefix if present
    if api_key.startswith("Bearer "):
        api_key = api_key[7:]

    db = get_db()

    # Look up candidate keys by prefix, then verify hash
    key_prefix = api_key[:8]
    candidates = db.fetchall(
        "SELECT k.*, t.name as tenant_name, t.plan, t.status as tenant_status "
        "FROM api_keys k JOIN tenants t ON k.tenant_id = t.id "
        "WHERE k.key_prefix = ? AND k.status = 'active'",
        (key_prefix,)
    )

    record = None
    for candidate in candidates:
        if verify_key_hash(api_key, candidate["key_hash"]):
            record = candidate
            break

    if not record:
        # Try legacy SHA-256 hash (no prefix match needed)
        legacy_hash = _legacy_hash_key(api_key)
        record = db.fetchone(
            "SELECT k.*, t.name as tenant_name, t.plan, t.status as tenant_status "
            "FROM api_keys k JOIN tenants t ON k.tenant_id = t.id "
            "WHERE k.key_hash = ? AND k.status = 'active'",
            (legacy_hash,)
        )
        if record:
            # Auto-migrate to PBKDF2
            new_hash = hash_key(api_key)
            db.execute("UPDATE api_keys SET key_hash = ? WHERE id = ?",
                       (new_hash, record["id"]))
            db.commit()

    if not record:
        raise AuthError("Invalid API key", 401)

    if record["tenant_status"] != "active":
        raise AuthError("Tenant suspended", 403)

    if record.get("expires_at") and record["expires_at"] < now_utc():
        raise AuthError("API key expired", 401)

    # Update last_used_at
    db.execute("UPDATE api_keys SET last_used_at = ? WHERE id = ?",
               (now_utc(), record["id"]))
    db.commit()

    import json
    scopes = json.loads(record.get("scopes", "[]"))

    return TenantContext(
        tenant_id=record["tenant_id"],
        tenant_name=record["tenant_name"],
        plan=record["plan"],
        user_id=record.get("user_id", ""),
        scopes=scopes,
        rate_limit=record.get("rate_limit", 60),
    )
