"""Business logic for API key lifecycle."""
from __future__ import annotations

import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from normbase.models.norm import APIKey
from security.auth.api_keys import generate_api_key, hash_api_key, verify_api_key_hash

DEFAULT_SCOPES = [
    "retrieval:plan",
    "norms:retrieve",
    "norms:check",
    "norms:report",
]


@dataclass
class IssuedKey:
    record: APIKey
    plain_key: str


class KeyService:
    """Service layer for API key operations."""

    def _utc_now(self) -> datetime:
        return datetime.now(timezone.utc).replace(tzinfo=None)

    def _to_utc(self, dt: datetime) -> datetime:
        if dt.tzinfo is None:
            return dt
        return dt.astimezone(timezone.utc).replace(tzinfo=None)

    def _ensure_not_expired(self, record: APIKey) -> bool:
        if record.expires_at is None:
            return True
        return self._to_utc(record.expires_at) > self._utc_now()

    def normalize_scopes(self, scopes: Optional[List[str]]) -> List[str]:
        normalized: List[str] = []
        for scope in scopes or []:
            text = str(scope).strip()
            if text and text not in normalized:
                normalized.append(text)
        return normalized or list(DEFAULT_SCOPES)

    def extract_bearer(self, authorization: Optional[str]) -> str:
        if not authorization:
            return ""
        raw = authorization.strip()
        if raw.lower().startswith("bearer "):
            return raw[7:].strip()
        return raw

    def agent_uuid_or_500(self, agent_id_text: Optional[str]) -> uuid.UUID:
        text = (agent_id_text or "").strip()
        if not text:
            raise HTTPException(status_code=500, detail="Stored API key missing agent_id UUID.")
        try:
            return uuid.UUID(text)
        except ValueError as exc:
            raise HTTPException(status_code=500, detail="Stored API key has invalid agent_id UUID.") from exc

    async def resolve_api_key_from_plain(
        self,
        db: AsyncSession,
        *,
        plain_api_key: str,
    ) -> APIKey:
        hashed = hash_api_key(plain_api_key)
        row = (
            await db.execute(select(APIKey).where(APIKey.key_hash == hashed))
        ).scalar_one_or_none()
        if not row:
            raise HTTPException(status_code=401, detail="Invalid API key")
        if not row.is_active or not self._ensure_not_expired(row):
            raise HTTPException(status_code=401, detail="API key inactive or expired")
        if not verify_api_key_hash(plain_api_key, row.key_hash):
            raise HTTPException(status_code=401, detail="Invalid API key")
        return row

    async def issue_api_key(
        self,
        db: AsyncSession,
        *,
        agent_id: uuid.UUID,
        name: Optional[str],
        expires_at: Optional[datetime],
        scopes: Optional[List[str]],
    ) -> IssuedKey:
        normalized_scopes = self.normalize_scopes(scopes)
        plain = generate_api_key()
        record = APIKey(
            key_hash=hash_api_key(plain),
            name=name or f"agent:{agent_id}",
            agent_id=str(agent_id),
            scopes=normalized_scopes,
            is_active=True,
            expires_at=expires_at,
        )
        db.add(record)
        await db.commit()
        await db.refresh(record)
        return IssuedKey(record=record, plain_key=plain)

    async def list_api_keys(
        self,
        db: AsyncSession,
        *,
        agent_id: Optional[uuid.UUID],
        include_inactive: bool,
    ) -> List[APIKey]:
        query = select(APIKey).order_by(APIKey.created_at.desc())
        if agent_id:
            query = query.where(APIKey.agent_id == str(agent_id))
        if not include_inactive:
            query = query.where(APIKey.is_active.is_(True))
        return list((await db.execute(query)).scalars().all())

    async def revoke_api_key(self, db: AsyncSession, *, key_id: uuid.UUID) -> APIKey:
        row = (await db.execute(select(APIKey).where(APIKey.id == key_id))).scalar_one_or_none()
        if not row:
            raise HTTPException(status_code=404, detail="API key not found")
        row.is_active = False
        await db.commit()
        await db.refresh(row)
        return row

    async def rotate_api_key(self, db: AsyncSession, *, key_id: uuid.UUID) -> IssuedKey:
        row = (await db.execute(select(APIKey).where(APIKey.id == key_id))).scalar_one_or_none()
        if not row:
            raise HTTPException(status_code=404, detail="API key not found")
        plain = generate_api_key()
        row.key_hash = hash_api_key(plain)
        row.is_active = True
        await db.commit()
        await db.refresh(row)
        return IssuedKey(record=row, plain_key=plain)
