"""
Settings Router
"""
import json
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel

from app.database import get_db
from app.models.setting import Setting
from app.models.user import User
from app.auth.dependencies import RequireAdmin
from app.auth.setting_crypto import (
    PROTECTED_SETTING_KEYS,
    decrypt_value,
    encrypt_value,
    is_protected,
)

router = APIRouter(prefix="/api/v1/settings", tags=["Settings"])

# Keys that may never be written through this API — owned by env vars or
# managed by the auth subsystem. Overwriting them would lock users out of
# MFA or compromise key isolation.
_DENYLISTED_KEYS = {"auth_provider_crypto_key"}

# Field names treated as secret-bearing inside JSON-encoded protected
# configs. Values for these subfields are redacted in GET responses; the
# admin must re-supply them on update to overwrite.
_SECRET_SUBFIELDS = {
    "password", "secret", "secret_key", "access_key",
    "api_key", "indexer_password", "bind_password",
    "token", "smtp_password",
}


def _redact_protected_value(key: str, stored: Optional[str]) -> Optional[str]:
    """Decrypt + redact secret subfields for safe GET responses."""
    if stored is None:
        return None
    try:
        plaintext = decrypt_value(stored)
    except Exception:
        return "***encrypted***"

    try:
        data = json.loads(plaintext)
    except (json.JSONDecodeError, TypeError):
        return "***redacted***"

    if isinstance(data, dict):
        for field in list(data.keys()):
            if field.lower() in _SECRET_SUBFIELDS and data[field]:
                data[field] = "***set***"
        return json.dumps(data)
    return "***redacted***"


def _serialize(setting: Setting) -> dict:
    value = setting.value
    if is_protected(setting.key):
        value = _redact_protected_value(setting.key, value)
    return {
        "key": setting.key,
        "value": value,
        "description": setting.description,
    }


# --- Schemas ---
class SettingResponse(BaseModel):
    key: str
    value: Optional[str]
    description: Optional[str]

    class Config:
        from_attributes = True

class SettingUpdate(BaseModel):
    value: str

# --- Endpoints ---

@router.get("", response_model=List[SettingResponse])
async def list_settings(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin)
):
    """List all system settings (secret subfields redacted)."""
    return [_serialize(s) for s in db.query(Setting).all()]

@router.get("/{key}", response_model=SettingResponse)
async def get_setting(
    key: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin)
):
    """Get a specific setting (secret subfields redacted for protected keys)."""
    setting = db.query(Setting).filter(Setting.key == key).first()
    if not setting:
        raise HTTPException(status_code=404, detail="Setting not found")
    return _serialize(setting)

@router.put("/{key}", response_model=SettingResponse)
async def update_setting(
    key: str,
    update_data: SettingUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin)
):
    """Update or Create a setting. Protected keys are encrypted at rest."""
    if key in _DENYLISTED_KEYS:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="This setting is managed outside the API and cannot be modified here",
        )

    value_to_store = update_data.value
    if is_protected(key):
        # If admin re-submitted a redacted value, refuse — they probably
        # didn't intend to overwrite secrets with the literal "***set***".
        try:
            parsed = json.loads(update_data.value)
            if isinstance(parsed, dict):
                for f in parsed:
                    if f.lower() in _SECRET_SUBFIELDS and parsed[f] == "***set***":
                        raise HTTPException(
                            status_code=400,
                            detail=(
                                f"Refusing to store redaction placeholder in '{f}'. "
                                "Re-submit the actual secret value."
                            ),
                        )
        except json.JSONDecodeError:
            pass
        value_to_store = encrypt_value(update_data.value)

    setting = db.query(Setting).filter(Setting.key == key).first()
    if not setting:
        setting = Setting(key=key, value=value_to_store)
        db.add(setting)
    else:
        setting.value = value_to_store

    db.commit()
    db.refresh(setting)
    return _serialize(setting)
