"""Tenant/app-scoped credential lifecycle."""

from __future__ import annotations

import re
from datetime import datetime, timezone
from typing import Any

from sqlalchemy import select

from config.control_plane import get_control_plane_session
from bridge_platform.secrets.encryption import VersionedAESCipher
from bridge_platform.tenants.models import App, Tenant, TenantSecret


SECRET_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_.-]{1,63}$")


class SecretsManager:
    """Store write-only credentials encrypted with tenant-bound AAD."""

    def __init__(self, cipher: VersionedAESCipher | None = None) -> None:
        self.cipher = cipher or VersionedAESCipher.from_environment()

    def put_secret(
        self, *, tenant_id: str, secret_name: str, secret_value: str,
        app_id: str | None = None, actor_id: str | None = None,
    ) -> TenantSecret:
        self._validate_input(secret_name, secret_value)
        encrypted = self.cipher.encrypt(
            secret_value,
            aad=self._aad(tenant_id, app_id, secret_name),
        )
        with get_control_plane_session() as session:
            self._validate_scope(session, tenant_id, app_id)
            record = session.execute(self._query(tenant_id, app_id, secret_name)).scalar_one_or_none()
            if record is None:
                record = TenantSecret(
                    tenant_id=tenant_id, app_id=app_id, secret_name=secret_name,
                    secret_value_encrypted=encrypted.ciphertext,
                )
                session.add(record)
            record.secret_value_encrypted = encrypted.ciphertext
            record.key_version = encrypted.key_version
            record.metadata_json = {
                "algorithm": "AES-256-GCM",
                "updated_by": actor_id,
                "updated_at": datetime.now(timezone.utc).isoformat(),
            }
            session.commit()
            session.refresh(record)
            return record

    def get_secret(self, *, tenant_id: str, secret_name: str, app_id: str | None = None) -> str | None:
        with get_control_plane_session() as session:
            record = session.execute(self._query(tenant_id, app_id, secret_name)).scalar_one_or_none()
            if record is None:
                return None
            return self.cipher.decrypt(
                record.secret_value_encrypted,
                aad=self._aad(tenant_id, app_id, secret_name),
            )

    def list_secret_metadata(self, *, tenant_id: str, app_id: str | None = None) -> list[dict[str, Any]]:
        with get_control_plane_session() as session:
            statement = select(TenantSecret).where(TenantSecret.tenant_id == tenant_id)
            if app_id is not None:
                statement = statement.where(TenantSecret.app_id == app_id)
            records = session.execute(statement.order_by(TenantSecret.app_id, TenantSecret.secret_name)).scalars().all()
            return [{
                "app_id": record.app_id,
                "secret_name": record.secret_name,
                "configured": True,
                "key_version": record.key_version,
                "updated_at": record.updated_at.isoformat(),
            } for record in records]

    def delete_secret(self, *, tenant_id: str, secret_name: str, app_id: str | None = None) -> bool:
        with get_control_plane_session() as session:
            record = session.execute(self._query(tenant_id, app_id, secret_name)).scalar_one_or_none()
            if record is None:
                return False
            session.delete(record)
            session.commit()
            return True

    @staticmethod
    def _query(tenant_id: str, app_id: str | None, secret_name: str):
        return select(TenantSecret).where(
            TenantSecret.tenant_id == tenant_id,
            TenantSecret.app_id == app_id,
            TenantSecret.secret_name == secret_name,
        ).limit(1)

    @staticmethod
    def _aad(tenant_id: str, app_id: str | None, secret_name: str) -> bytes:
        return f"tenant={tenant_id}\0app={app_id or '-'}\0secret={secret_name}".encode("utf-8")

    @staticmethod
    def _validate_input(secret_name: str, secret_value: str) -> None:
        if not SECRET_NAME_PATTERN.fullmatch(secret_name):
            raise ValueError("Invalid secret name")
        if not secret_value or len(secret_value.encode("utf-8")) > 16_384:
            raise ValueError("Secret value must contain between 1 and 16384 bytes")

    @staticmethod
    def _validate_scope(session, tenant_id: str, app_id: str | None) -> None:
        if session.get(Tenant, tenant_id) is None:
            raise LookupError(f"Tenant not found: {tenant_id}")
        if app_id and session.get(App, app_id) is None:
            raise LookupError(f"App not found: {app_id}")
