from __future__ import annotations

from datetime import datetime
from typing import Any, Literal

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field, SecretStr
from sqlalchemy.orm import Session

from govoplan_core.auth import ApiPrincipal, get_api_principal, has_scope
from govoplan_core.db.session import get_session
from govoplan_core.security.credential_envelopes import (
    CredentialEnvelope,
    CredentialEnvelopeError,
    create_credential_envelope,
    credential_envelope_summary,
    get_managed_credential_envelope,
    list_managed_credential_envelopes,
    normalize_credential_scope,
    retire_credential_envelope,
    update_credential_envelope,
)


CredentialScopeType = Literal["system", "tenant", "group", "user", "campaign"]


class CredentialEnvelopeResponse(BaseModel):
    id: str
    tenant_id: str | None = None
    scope_type: CredentialScopeType
    scope_id: str | None = None
    name: str
    description: str | None = None
    credential_kind: str
    public_data: dict[str, Any] = Field(default_factory=dict)
    secret_keys: list[str] = Field(default_factory=list)
    secret_configured: bool = False
    allowed_modules: list[str] = Field(default_factory=list)
    allowed_server_refs: list[str] = Field(default_factory=list)
    inherit_to_lower_scopes: bool = False
    is_active: bool = True
    revision: str
    created_at: datetime | None = None
    updated_at: datetime | None = None
    deleted_at: datetime | None = None


class CredentialEnvelopeListResponse(BaseModel):
    credentials: list[CredentialEnvelopeResponse] = Field(default_factory=list)


class CredentialEnvelopeCreateRequest(BaseModel):
    scope_type: CredentialScopeType = "tenant"
    scope_id: str | None = Field(default=None, max_length=255)
    name: str = Field(min_length=1, max_length=255)
    description: str | None = None
    credential_kind: str = Field(default="username_password", max_length=40)
    public_data: dict[str, Any] = Field(default_factory=dict)
    secret_data: dict[str, SecretStr] = Field(default_factory=dict)
    allowed_modules: list[str] = Field(default_factory=list)
    allowed_server_refs: list[str] = Field(default_factory=list)
    inherit_to_lower_scopes: bool = False
    is_active: bool = True


class CredentialEnvelopeUpdateRequest(BaseModel):
    name: str | None = Field(default=None, min_length=1, max_length=255)
    description: str | None = None
    credential_kind: str | None = Field(default=None, max_length=40)
    public_data: dict[str, Any] | None = None
    secret_data: dict[str, SecretStr] | None = None
    clear_secret: bool = False
    allowed_modules: list[str] | None = None
    allowed_server_refs: list[str] | None = None
    inherit_to_lower_scopes: bool | None = None
    is_active: bool | None = None


router = APIRouter(prefix="/credentials", tags=["credentials"])


@router.get("", response_model=CredentialEnvelopeListResponse)
def list_credentials(
    scope_type: CredentialScopeType = Query(default="tenant"),
    scope_id: str | None = Query(default=None),
    include_inactive: bool = Query(default=False),
    principal: ApiPrincipal = Depends(get_api_principal),
    session: Session = Depends(get_session),
) -> CredentialEnvelopeListResponse:
    tenant_id, normalized_scope_type, normalized_scope_id = _target_scope(
        principal,
        scope_type=scope_type,
        scope_id=scope_id,
        write=False,
    )
    rows = list_managed_credential_envelopes(
        session,
        tenant_id=tenant_id,
        include_inactive=include_inactive,
    )
    return CredentialEnvelopeListResponse(
        credentials=[
            _response(row)
            for row in rows
            if row.scope_type == normalized_scope_type
            and row.scope_id == normalized_scope_id
        ]
    )


@router.post(
    "",
    response_model=CredentialEnvelopeResponse,
    status_code=status.HTTP_201_CREATED,
)
def create_credential(
    payload: CredentialEnvelopeCreateRequest,
    principal: ApiPrincipal = Depends(get_api_principal),
    session: Session = Depends(get_session),
) -> CredentialEnvelopeResponse:
    tenant_id, scope_type, scope_id = _target_scope(
        principal,
        scope_type=payload.scope_type,
        scope_id=payload.scope_id,
        write=True,
    )
    try:
        row = create_credential_envelope(
            session,
            tenant_id=tenant_id,
            scope_type=scope_type,
            scope_id=scope_id,
            name=payload.name,
            description=payload.description,
            credential_kind=payload.credential_kind,
            public_data=payload.public_data,
            secret_data=_secret_values(payload.secret_data),
            allowed_modules=payload.allowed_modules,
            allowed_server_refs=payload.allowed_server_refs,
            inherit_to_lower_scopes=payload.inherit_to_lower_scopes,
            is_active=payload.is_active,
            user_id=principal.membership_id,
            metadata={"created_by_module": "core"},
        )
        session.commit()
        session.refresh(row)
        return _response(row)
    except CredentialEnvelopeError as exc:
        session.rollback()
        raise _credential_error(exc) from exc


@router.patch("/{credential_id}", response_model=CredentialEnvelopeResponse)
def update_credential(
    credential_id: str,
    payload: CredentialEnvelopeUpdateRequest,
    principal: ApiPrincipal = Depends(get_api_principal),
    session: Session = Depends(get_session),
) -> CredentialEnvelopeResponse:
    try:
        row = _managed_row_for_write(session, principal, credential_id)
        row = update_credential_envelope(
            session,
            row,
            name=payload.name,
            description=payload.description,
            description_supplied="description" in payload.model_fields_set,
            credential_kind=payload.credential_kind,
            public_data=payload.public_data,
            secret_data=(
                _secret_values(payload.secret_data)
                if payload.secret_data is not None
                else None
            ),
            clear_secret=payload.clear_secret,
            allowed_modules=payload.allowed_modules,
            allowed_server_refs=payload.allowed_server_refs,
            inherit_to_lower_scopes=payload.inherit_to_lower_scopes,
            is_active=payload.is_active,
            user_id=principal.membership_id,
        )
        session.commit()
        session.refresh(row)
        return _response(row)
    except CredentialEnvelopeError as exc:
        session.rollback()
        raise _credential_error(exc) from exc


@router.delete("/{credential_id}", response_model=CredentialEnvelopeResponse)
def delete_credential(
    credential_id: str,
    principal: ApiPrincipal = Depends(get_api_principal),
    session: Session = Depends(get_session),
) -> CredentialEnvelopeResponse:
    try:
        row = _managed_row_for_write(session, principal, credential_id)
        retire_credential_envelope(
            session,
            row,
            user_id=principal.membership_id,
        )
        response = _response(row)
        session.commit()
        return response
    except CredentialEnvelopeError as exc:
        session.rollback()
        raise _credential_error(exc) from exc


def _managed_row_for_write(
    session: Session,
    principal: ApiPrincipal,
    credential_id: str,
) -> CredentialEnvelope:
    system_row = None
    if _can_manage_system_credentials(principal):
        try:
            system_row = get_managed_credential_envelope(
                session,
                credential_id=credential_id,
                tenant_id=None,
                for_update=True,
            )
        except CredentialEnvelopeError:
            pass
    row = system_row or get_managed_credential_envelope(
        session,
        credential_id=credential_id,
        tenant_id=principal.tenant_id,
        for_update=True,
    )
    _target_scope(
        principal,
        scope_type=row.scope_type,
        scope_id=row.scope_id,
        write=True,
    )
    return row


def _target_scope(
    principal: ApiPrincipal,
    *,
    scope_type: str,
    scope_id: str | None,
    write: bool,
) -> tuple[str | None, str, str | None]:
    requested_type = str(scope_type or "tenant").strip().casefold()
    requested_id = str(scope_id).strip() if scope_id else None
    if requested_type == "system":
        if not (
            _can_manage_system_credentials(principal)
            if write
            else _can_read_system_credentials(principal)
        ):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="System credential permission is required.",
            )
        return _normalize_target_scope(
            tenant_id=None,
            scope_type="system",
            scope_id=None,
        )

    if requested_type == "user" and requested_id == principal.membership_id:
        own_scope = (
            has_scope(principal, "access:credential:manage_own")
            or has_scope(principal, "mail:secret:manage_own")
        )
        if own_scope:
            return _normalize_target_scope(
                tenant_id=principal.tenant_id,
                scope_type=requested_type,
                scope_id=requested_id,
            )

    allowed = (
        _can_manage_tenant_credentials(principal)
        if write
        else _can_read_tenant_credentials(principal)
    )
    if not allowed:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Tenant credential permission is required.",
        )
    return _normalize_target_scope(
        tenant_id=principal.tenant_id,
        scope_type=requested_type,
        scope_id=requested_id,
    )


def _normalize_target_scope(
    *,
    tenant_id: str | None,
    scope_type: str,
    scope_id: str | None,
) -> tuple[str | None, str, str | None]:
    try:
        return normalize_credential_scope(
            tenant_id=tenant_id,
            scope_type=scope_type,
            scope_id=scope_id,
        )
    except CredentialEnvelopeError as exc:
        raise _credential_error(exc) from exc


def _can_read_system_credentials(principal: ApiPrincipal) -> bool:
    return any(
        has_scope(principal, scope)
        for scope in (
            "access:system_credential:read",
            "access:system_setting:read",
            "system:settings:read",
        )
    )


def _can_manage_system_credentials(principal: ApiPrincipal) -> bool:
    return any(
        has_scope(principal, scope)
        for scope in (
            "access:system_credential:write",
            "access:system_setting:write",
            "system:settings:write",
        )
    )


def _can_read_tenant_credentials(principal: ApiPrincipal) -> bool:
    return any(
        has_scope(principal, scope)
        for scope in (
            "access:credential:read",
            "access:setting:read",
            "admin:settings:read",
            "mail_servers:read",
        )
    )


def _can_manage_tenant_credentials(principal: ApiPrincipal) -> bool:
    return any(
        has_scope(principal, scope)
        for scope in (
            "access:credential:write",
            "access:setting:write",
            "admin:settings:write",
            "mail_servers:manage_credentials",
        )
    )


def _secret_values(values: dict[str, SecretStr]) -> dict[str, str]:
    return {
        str(key): value.get_secret_value()
        for key, value in values.items()
        if str(key).strip() and value.get_secret_value()
    }


def _response(row: CredentialEnvelope) -> CredentialEnvelopeResponse:
    return CredentialEnvelopeResponse.model_validate(
        credential_envelope_summary(row)
    )


def _credential_error(exc: CredentialEnvelopeError) -> HTTPException:
    return HTTPException(
        status_code=status.HTTP_400_BAD_REQUEST,
        detail=str(exc),
    )


__all__ = ["router"]
