import re
import uuid
from typing import Annotated, Literal

from fastapi import APIRouter, Cookie, Header, Request
from pydantic import BaseModel, Field, field_validator
from sqlalchemy import func, select

from .auth import AuthDep, SessionDep, _audit, _require_csrf
from .authorization import authorize
from .errors import AppError
from .health import AIHealthDep
from .models import Agent, AIModelProfile, DeviceSession

router = APIRouter(prefix="/api/v1/model-profiles", tags=["model-profiles"])


class ModelProfileInput(BaseModel):
    profile_key: str = Field(min_length=2, max_length=80)
    display_name: str = Field(min_length=1, max_length=160)
    provider: Literal["openai"] = "openai"
    model_id: str = Field(min_length=1, max_length=160)
    enabled: bool = True
    capabilities: list[Literal["planning", "text", "vision"]] = Field(
        default_factory=lambda: ["planning", "text"], min_length=1, max_length=3
    )

    @field_validator("profile_key")
    @classmethod
    def validate_key(cls, value: str) -> str:
        value = value.strip().lower()
        if not re.fullmatch(r"[a-z][a-z0-9_-]+", value):
            raise ValueError("Profile key must be a stable lowercase identifier")
        return value

    @field_validator("display_name", "model_id")
    @classmethod
    def strip_text(cls, value: str) -> str:
        value = value.strip()
        if not value:
            raise ValueError("Value cannot be blank")
        return value


def _data(profile: AIModelProfile, provider_state: str) -> dict:
    return {
        "id": str(profile.id),
        "profile_key": profile.profile_key,
        "display_name": profile.display_name,
        "provider": profile.provider,
        "model_id": profile.model_id,
        "enabled": profile.enabled,
        "capabilities": profile.capabilities,
        "provider_state": provider_state,
        "created_at": profile.created_at,
        "updated_at": profile.updated_at,
    }


async def _csrf(session: SessionDep, auth: AuthDep, cookie: str | None, header: str | None):
    _require_csrf(await session.get(DeviceSession, auth.session_id), cookie, header)


async def _load(session: SessionDep, workspace_id: uuid.UUID, profile_id: uuid.UUID):
    profile = await session.scalar(select(AIModelProfile).where(
        AIModelProfile.id == profile_id, AIModelProfile.workspace_id == workspace_id
    ))
    if not profile:
        raise AppError("MODEL_PROFILE_NOT_FOUND", "The model profile is unavailable.", 404)
    return profile


@router.get("")
async def list_model_profiles(auth: AuthDep, session: SessionDep, ai_probe: AIHealthDep):
    await authorize(session, workspace_id=auth.workspace_id, user_id=auth.user_id,
                    permission="settings.read")
    state = await ai_probe.status()
    profiles = list((await session.scalars(select(AIModelProfile).where(
        AIModelProfile.workspace_id == auth.workspace_id
    ).order_by(AIModelProfile.display_name))).all())
    return {
        "success": True,
        "provider_state": state,
        "profiles": [_data(profile, state) for profile in profiles],
    }


@router.post("", status_code=201)
async def create_model_profile(
    payload: ModelProfileInput, request: Request, auth: AuthDep, session: SessionDep,
    ai_probe: AIHealthDep,
    hayva_csrf: Annotated[str | None, Cookie()] = None,
    x_csrf_token: Annotated[str | None, Header()] = None,
):
    await authorize(session, workspace_id=auth.workspace_id, user_id=auth.user_id,
                    permission="settings.modify")
    await _csrf(session, auth, hayva_csrf, x_csrf_token)
    if await session.scalar(select(AIModelProfile.id).where(
        AIModelProfile.workspace_id == auth.workspace_id,
        AIModelProfile.profile_key == payload.profile_key,
    )):
        raise AppError("MODEL_PROFILE_CONFLICT", "This model profile key already exists.", 409)
    profile = AIModelProfile(
        id=uuid.uuid4(), workspace_id=auth.workspace_id, created_by_user_id=auth.user_id,
        **payload.model_dump(),
    )
    session.add(profile)
    await _audit(
        session, workspace_id=auth.workspace_id, actor_id=auth.user_id,
        event_type="model_profile.created", request_id=request.state.request_id,
        data={"model_profile_id": str(profile.id), "profile_key": profile.profile_key,
              "provider": profile.provider},
    )
    await session.commit()
    await session.refresh(profile)
    return {"success": True, "profile": _data(profile, await ai_probe.status())}


@router.put("/{profile_id}")
async def update_model_profile(
    profile_id: uuid.UUID, payload: ModelProfileInput, request: Request, auth: AuthDep,
    session: SessionDep, ai_probe: AIHealthDep,
    hayva_csrf: Annotated[str | None, Cookie()] = None,
    x_csrf_token: Annotated[str | None, Header()] = None,
):
    await authorize(session, workspace_id=auth.workspace_id, user_id=auth.user_id,
                    permission="settings.modify")
    await _csrf(session, auth, hayva_csrf, x_csrf_token)
    profile = await _load(session, auth.workspace_id, profile_id)
    conflict = await session.scalar(select(AIModelProfile.id).where(
        AIModelProfile.workspace_id == auth.workspace_id,
        AIModelProfile.profile_key == payload.profile_key,
        AIModelProfile.id != profile.id,
    ))
    if conflict:
        raise AppError("MODEL_PROFILE_CONFLICT", "This model profile key already exists.", 409)
    for key, value in payload.model_dump().items():
        setattr(profile, key, value)
    await _audit(
        session, workspace_id=auth.workspace_id, actor_id=auth.user_id,
        event_type="model_profile.updated", request_id=request.state.request_id,
        data={"model_profile_id": str(profile.id), "enabled": profile.enabled},
    )
    await session.commit()
    await session.refresh(profile)
    return {"success": True, "profile": _data(profile, await ai_probe.status())}


@router.delete("/{profile_id}")
async def delete_model_profile(
    profile_id: uuid.UUID, request: Request, auth: AuthDep, session: SessionDep,
    hayva_csrf: Annotated[str | None, Cookie()] = None,
    x_csrf_token: Annotated[str | None, Header()] = None,
):
    await authorize(session, workspace_id=auth.workspace_id, user_id=auth.user_id,
                    permission="settings.modify")
    await _csrf(session, auth, hayva_csrf, x_csrf_token)
    profile = await _load(session, auth.workspace_id, profile_id)
    references = await session.scalar(select(func.count(Agent.id)).where(
        Agent.workspace_id == auth.workspace_id,
        (Agent.preferred_model == profile.profile_key)
        | (Agent.fallback_model == profile.profile_key),
    ))
    if references:
        raise AppError("MODEL_PROFILE_REFERENCED", "This model profile is assigned to an agent.", 409)
    await _audit(
        session, workspace_id=auth.workspace_id, actor_id=auth.user_id,
        event_type="model_profile.deleted", request_id=request.state.request_id,
        data={"model_profile_id": str(profile.id), "profile_key": profile.profile_key},
    )
    await session.delete(profile)
    await session.commit()
    return {"success": True, "deleted_model_profile_id": str(profile_id)}
