"""Settings › Secrets — sensitive values stored once and used by reference
({{secret.NAME}}); see app/crud/secrets.py for how and where they're used.

Write-only by design: you can create a secret, replace its value, describe or
delete it — but no endpoint (and no role, not even Administrator) ever gets
the value back. Every change is audit-logged; the value never is."""

import uuid

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.crypto import encrypt_secret
from app.core.database import get_db
from app.core.permissions import deny_vendor, require_permission
from app.core.security import AuthContext, get_auth_context
from app.crud.audit import write_audit_log
from app.crud.secrets import referenced_names
from app.models.automation import Task
from app.models.data_query import DataQuery
from app.models.identity import User
from app.models.reporting import Connector
from app.models.secret import Secret
from app.schemas.secrets import SecretCreate, SecretRead, SecretUpdate, SecretUsage

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


async def _usage(db: AsyncSession, tenant_id: uuid.UUID) -> dict[str, list[SecretUsage]]:
    """Which tasks and connectors reference each secret name."""
    used: dict[str, list[SecretUsage]] = {}
    for task in (await db.execute(select(Task).where(Task.tenant_id == tenant_id))).scalars():
        for name in referenced_names(task.nodes or []):
            used.setdefault(name, []).append(SecretUsage(kind="task", id=task.id, name=task.name))
    for connector in (await db.execute(select(Connector).where(Connector.tenant_id == tenant_id))).scalars():
        for name in referenced_names(connector.configuration or {}):
            used.setdefault(name, []).append(SecretUsage(kind="connector", id=connector.id, name=connector.name))
    for query in (await db.execute(select(DataQuery).where(DataQuery.tenant_id == tenant_id))).scalars():
        for name in referenced_names(query.definition or {}):
            used.setdefault(name, []).append(SecretUsage(kind="data_query", id=query.id, name=query.name))
    return used


async def _read(db: AsyncSession, secret: Secret, used: dict[str, list[SecretUsage]] | None = None) -> SecretRead:
    updated_by = (await db.execute(select(User.name).where(User.id == secret.updated_by))).scalar_one_or_none() if secret.updated_by else None
    if used is None:
        used = await _usage(db, secret.tenant_id)
    return SecretRead(
        id=secret.id,
        name=secret.name,
        description=secret.description,
        created_at=secret.created_at,
        updated_at=secret.updated_at,
        updated_by_name=updated_by,
        used_by=used.get(secret.name, []),
    )


async def _get(db: AsyncSession, tenant_id: uuid.UUID, secret_id: uuid.UUID) -> Secret:
    secret = (await db.execute(select(Secret).where(Secret.tenant_id == tenant_id, Secret.id == secret_id))).scalar_one_or_none()
    if secret is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found")
    return secret


@router.get("", response_model=list[SecretRead])
async def list_secrets(
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("secrets", "view")),
    __: AuthContext = Depends(deny_vendor()),
) -> list[SecretRead]:
    secrets = (await db.execute(select(Secret).where(Secret.tenant_id == ctx.tenant_id).order_by(Secret.name))).scalars().all()
    used = await _usage(db, ctx.tenant_id)
    return [await _read(db, s, used) for s in secrets]


@router.get("/names", response_model=list[str])
async def list_secret_names(
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    __: AuthContext = Depends(deny_vendor()),
) -> list[str]:
    """Just the names — for the Task Designer's and Connectors' pickers, so
    anyone building those can reference a secret without being able to
    manage (or see) it."""
    return list((await db.execute(select(Secret.name).where(Secret.tenant_id == ctx.tenant_id).order_by(Secret.name))).scalars().all())


@router.post("", response_model=SecretRead, status_code=201)
async def create_secret(
    payload: SecretCreate,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("secrets", "create")),
    __: AuthContext = Depends(deny_vendor()),
) -> SecretRead:
    exists = (await db.execute(select(Secret.id).where(Secret.tenant_id == ctx.tenant_id, Secret.name == payload.name))).scalar_one_or_none()
    if exists:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=f"A secret named {payload.name} already exists — replace its value instead.")
    secret = Secret(
        tenant_id=ctx.tenant_id,
        name=payload.name,
        description=(payload.description or "").strip() or None,
        value_encrypted=encrypt_secret(payload.value),
        created_by=ctx.user_id,
        updated_by=ctx.user_id,
    )
    db.add(secret)
    await db.flush()
    await db.refresh(secret)
    await write_audit_log(db, ctx.tenant_id, ctx.user_id, "secret.create", "secret", secret.id, {"name": secret.name})
    return await _read(db, secret)


@router.patch("/{secret_id}", response_model=SecretRead)
async def update_secret(
    secret_id: uuid.UUID,
    payload: SecretUpdate,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("secrets", "edit")),
    __: AuthContext = Depends(deny_vendor()),
) -> SecretRead:
    secret = await _get(db, ctx.tenant_id, secret_id)
    changed: list[str] = []
    if "description" in payload.model_fields_set:
        secret.description = (payload.description or "").strip() or None
        changed.append("description")
    if payload.value is not None:
        secret.value_encrypted = encrypt_secret(payload.value)
        changed.append("value")
    if changed:
        secret.updated_by = ctx.user_id
        await db.flush()
        await db.refresh(secret)
        await write_audit_log(db, ctx.tenant_id, ctx.user_id, "secret.update", "secret", secret.id, {"name": secret.name, "changed": changed})
    return await _read(db, secret)


@router.delete("/{secret_id}", status_code=204)
async def delete_secret(
    secret_id: uuid.UUID,
    ctx: AuthContext = Depends(get_auth_context),
    db: AsyncSession = Depends(get_db),
    _: AuthContext = Depends(require_permission("secrets", "delete")),
    __: AuthContext = Depends(deny_vendor()),
) -> None:
    secret = await _get(db, ctx.tenant_id, secret_id)
    name = secret.name
    await db.delete(secret)
    await db.flush()
    await write_audit_log(db, ctx.tenant_id, ctx.user_id, "secret.delete", "secret", secret_id, {"name": name})
