"""
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"])


class SyslogTestRequest(BaseModel):
    host: str
    port: int = 514
    protocol: str = "udp"
    facility: int = 16


@router.post("/syslog/test")
async def test_syslog(
    payload: SyslogTestRequest,
    current_user: User = Depends(RequireAdmin),
):
    """Send a one-off test message to the given syslog target. Admin only."""
    from app.services.syslog_service import send_test
    ok, detail = send_test(payload.model_dump())
    return {"ok": ok, "detail": detail}

# 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", "client_secret",
}


def _redact_protected_value(key: str, stored: Optional[str]) -> Optional[str]:
    """Decrypt + redact secret subfields for safe GET responses."""
    if not stored:
        return None
    try:
        if not decrypt_value(stored):
            return None  # empty after decrypt = treated as unset
    except Exception:
        pass
    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):
        # For protected JSON configs, MERGE secret subfields: if the admin
        # left a secret blank or re-submitted the redaction placeholder
        # ("***set***"), keep the previously-stored secret instead of
        # wiping it. Lets the operator edit non-secret fields (host, ids,
        # toggles) without re-typing the password/secret every time.
        try:
            parsed = json.loads(update_data.value)
        except json.JSONDecodeError:
            parsed = None
        if isinstance(parsed, dict):
            existing_row = db.query(Setting).filter(Setting.key == key).first()
            existing_cfg = {}
            if existing_row and existing_row.value:
                try:
                    existing_cfg = json.loads(decrypt_value(existing_row.value))
                except Exception:
                    existing_cfg = {}
            for f in list(parsed.keys()):
                if f.lower() in _SECRET_SUBFIELDS and (parsed[f] in ("", "***set***", None)):
                    if existing_cfg.get(f):
                        parsed[f] = existing_cfg[f]   # preserve stored secret
                    else:
                        parsed.pop(f, None)           # nothing to keep → drop
            update_data.value = json.dumps(parsed)
        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)
