"""FC-3b: /api/credentials — CRUD with X-Extension-Key auth.

Browser requests (no header) are accepted per the homelab posture —
there is no user-session model in FC. The X-Extension-Key check
exists to give the extension a revocable path: a request that
supplies the header must match `app_setting.extension_api_key`.
"""

from pathlib import Path

from quart import Blueprint, jsonify, request
from sqlalchemy import select

from ..extensions import get_session
from ..models import AppSetting, Credential
from ..services.credential_crypto import CredentialCrypto
from ..services.credential_service import (
    CredentialService,
    EmptyDataError,
    UnknownPlatformError,
    WrongAuthTypeError,
)
from ._responses import error_response as _bad

credentials_bp = Blueprint("credentials", __name__, url_prefix="/api/credentials")


# Production key path; tests pass `cookies_dir=tmp_path` via the
# service constructor (the API uses the default). The Fernet key
# file is also at `/images/secrets/credential_key.b64`.
_KEY_PATH = Path("/images/secrets/credential_key.b64")
_crypto: CredentialCrypto | None = None


def _get_crypto() -> CredentialCrypto:
    global _crypto
    if _crypto is None:
        _crypto = CredentialCrypto(_KEY_PATH)
    return _crypto


async def _ext_key_ok(session) -> bool:
    """If X-Extension-Key is supplied, it must match the stored value.
    Missing header → True (browser path; accepted per homelab posture).
    """
    supplied = request.headers.get("X-Extension-Key")
    if supplied is None:
        return True
    stored = (await session.execute(
        select(AppSetting.value).where(AppSetting.key == "extension_api_key")
    )).scalar_one_or_none()
    return stored is not None and supplied == stored


@credentials_bp.route("", methods=["GET"])
async def list_credentials():
    async with get_session() as session:
        if not await _ext_key_ok(session):
            return _bad("unauthorized", status=401)
        records = await CredentialService(session, _get_crypto()).list()
    return jsonify([r.to_dict() for r in records])


@credentials_bp.route("/<platform>", methods=["GET"])
async def get_credential(platform: str):
    async with get_session() as session:
        if not await _ext_key_ok(session):
            return _bad("unauthorized", status=401)
        record = await CredentialService(session, _get_crypto()).get(platform)
    if record is None:
        return _bad("not_found", status=404)
    return jsonify(record.to_dict())


@credentials_bp.route("", methods=["POST"])
async def upsert_credential():
    body = await request.get_json()
    if not isinstance(body, dict):
        return _bad("invalid_body", detail="body must be a JSON object")
    try:
        platform = body["platform"]
        credential_type = body["credential_type"]
        data = body["data"]
    except KeyError:
        return _bad("invalid_body", detail="platform, credential_type, data are required")
    if not isinstance(data, str):
        return _bad("invalid_body", detail="data must be a string")

    async with get_session() as session:
        if not await _ext_key_ok(session):
            return _bad("unauthorized", status=401)
        existed = (await session.execute(
            select(Credential).where(Credential.platform == platform)
        )).scalar_one_or_none() is not None
        svc = CredentialService(session, _get_crypto())
        try:
            record = await svc.upsert(
                platform=platform, credential_type=credential_type, data=data,
            )
        except UnknownPlatformError as exc:
            return _bad("unknown_platform", detail=str(exc))
        except WrongAuthTypeError as exc:
            return _bad("wrong_auth_type", detail=str(exc), expected=exc.expected)
        except EmptyDataError as exc:
            return _bad("empty_data", detail=str(exc))
    return jsonify(record.to_dict()), (200 if existed else 201)


@credentials_bp.route("/<platform>", methods=["DELETE"])
async def delete_credential(platform: str):
    async with get_session() as session:
        if not await _ext_key_ok(session):
            return _bad("unauthorized", status=401)
        svc = CredentialService(session, _get_crypto())
        try:
            await svc.delete(platform)
        except LookupError:
            return _bad("not_found", status=404)
    return "", 204


@credentials_bp.route("/<platform>/verify", methods=["POST"])
async def verify_credential(platform: str):
    """Test the stored credential against one of the platform's enabled sources,
    WITHOUT downloading. Routes through the platform's backend
    (download_backends.verify_credential) — native ingester for Patreon, an
    authenticated API page; gallery-dl --simulate for the rest. On success
    stamps last_verified. Returns {valid: bool|null, reason, last_verified?};
    valid=null means "couldn't test" (no credential, no enabled source, or an
    inconclusive network/drift result)."""
    from ..models import Artist, Source
    from ..services.download_backends import verify_source_credential

    async with get_session() as session:
        if not await _ext_key_ok(session):
            return _bad("unauthorized", status=401)
        svc = CredentialService(session, _get_crypto())
        record = await svc.get(platform)
        if record is None:
            return jsonify({"valid": None, "reason": "No credential stored for this platform."})

        # Pick an enabled source for this platform to point the probe at.
        row = (await session.execute(
            select(Source, Artist)
            .join(Artist, Artist.id == Source.artist_id)
            .where(Source.platform == platform, Source.enabled.is_(True))
            .order_by(Source.id.asc())
        )).first()
        if row is None:
            return jsonify({
                "valid": None,
                "reason": "No enabled source for this platform to verify against — add a subscription first.",
            })
        source, artist = row

        cookies_path = await svc.get_cookies_path(platform)
        auth_token = await svc.get_token(platform)

    ok, message = await verify_source_credential(
        platform=platform,
        url=source.url,
        artist_slug=artist.slug,
        config_overrides=source.config_overrides or {},
        cookies_path=str(cookies_path) if cookies_path else None,
        auth_token=auth_token,
        images_root=Path("/images"),
    )

    last_verified = None
    if ok:
        async with get_session() as session:
            ts = await CredentialService(session, _get_crypto()).mark_verified(platform)
            last_verified = ts.isoformat() if ts else None
    return jsonify({"valid": ok, "reason": message, "last_verified": last_verified})
