"""Application settings (admin): random amount/shipping ranges, invoice number format,
default per-account limits, send pacing, attachment filename prefixes, compliance toggles."""
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db, require_csrf
from app.core.config import settings as cfg
from app.core.security import log_audit
from app.models import ApplicationSettings, User
from app.services.campaign_engine import get_setting

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


class SettingIn(BaseModel):
    key: str = Field(min_length=1, max_length=150)
    value: object = None
    category: str = "general"
    description: str = ""


class BatchSettingsIn(BaseModel):
    values: dict


# Known settings keys with validation constraints
SETTING_DEFS = {
    "amount_min": {"type": "float", "min": 0, "max": 100000, "default": cfg.AMOUNT_MIN,
                   "label": "Random amount minimum ($)", "category": "financial"},
    "amount_max": {"type": "float", "min": 0, "max": 100000, "default": cfg.AMOUNT_MAX,
                   "label": "Random amount maximum ($)", "category": "financial"},
    "shipping_min": {"type": "float", "min": 0, "max": 10000, "default": cfg.SHIPPING_MIN,
                     "label": "Random shipping minimum ($)", "category": "financial"},
    "shipping_max": {"type": "float", "min": 0, "max": 10000, "default": cfg.SHIPPING_MAX,
                     "label": "Random shipping maximum ($)", "category": "financial"},
    "invoice_format": {"type": "str", "max_len": 80, "default": cfg.INVOICE_NUMBER_FORMAT,
                       "label": "Invoice number format (tokens: {prefix},{R#},{r#},{d#},{seq4},{date},{hash})",
                       "category": "financial"},
    "invoice_prefix": {"type": "str", "max_len": 12, "default": "INV",
                       "label": "Invoice prefix", "category": "financial"},
    "default_daily_limit": {"type": "int", "min": 1, "max": 2000, "default": 500,
                            "label": "Default per-account daily send limit", "category": "limits"},
    "default_campaign_limit": {"type": "int", "min": 1, "max": 2000, "default": 200,
                               "label": "Default per-account per-campaign limit", "category": "limits"},
    "default_max_per_account": {"type": "int", "min": 1, "max": 2000, "default": 200,
                                "label": "Default max sends per account per campaign", "category": "limits"},
    "default_send_interval": {"type": "float", "min": 0.5, "max": 3600, "default": 1.0,
                              "label": "Default seconds between sends per account", "category": "limits"},
    "default_rate_per_minute": {"type": "int", "min": 1, "max": 600, "default": 60,
                                "label": "Default max sends per minute (per account)", "category": "limits"},
    "attachment_kind": {"type": "str", "max_len": 20, "default": "invoice",
                        "label": "Attachment filename prefix (invoice|order|receipt)",
                        "category": "attachments"},
    "unsubscribe_enabled": {"type": "bool", "default": True,
                            "label": "Enable unsubscribe links globally", "category": "compliance"},
    "unsubscribe_base_url": {"type": "str", "max_len": 300, "default": "",
                             "label": "Base URL for unsubscribe links (e.g. https://your-domain.com)",
                             "category": "compliance"},
    "company_name": {"type": "str", "max_len": 200, "default": "",
                     "label": "Company name for %CompanyName% default", "category": "general"},
    "test_email_default": {"type": "str", "max_len": 320, "default": "",
                           "label": "Default test email recipient", "category": "general"},
}


def _validate_value(key: str, value) -> tuple:
    d = SETTING_DEFS.get(key)
    if not d:
        return False, f"Unknown setting key: {key}"
    t = d["type"]
    try:
        if t == "float":
            v = float(value)
            if "min" in d and v < d["min"]:
                return False, f"Value below minimum {d['min']}"
            if "max" in d and v > d["max"]:
                return False, f"Value above maximum {d['max']}"
            return True, v
        if t == "int":
            v = int(value)
            if "min" in d and v < d["min"]:
                return False, f"Value below minimum {d['min']}"
            if "max" in d and v > d["max"]:
                return False, f"Value above maximum {d['max']}"
            return True, v
        if t == "bool":
            if isinstance(value, str):
                v = value.strip().lower() in ("1", "true", "yes", "on")
            else:
                v = bool(value)
            return True, v
        if t == "str":
            v = str(value).strip()
            if "max_len" in d and len(v) > d["max_len"]:
                return False, f"Max length {d['max_len']}"
            if key == "attachment_kind" and v not in ("invoice", "order", "receipt", "document"):
                return False, "attachment_kind must be invoice|order|receipt|document"
            if key == "invoice_format" and ("{" not in v and v):
                return False, "Format must contain tokens like {prefix}{R#}{seq4}"
            return True, v
    except (TypeError, ValueError):
        return False, f"Value must be {t}"
    return False, "Invalid value"


@router.get("/list")
async def list_settings(db: AsyncSession = Depends(get_db),
                        user: User = Depends(get_current_user)):
    """All settings with current values (DB overrides) and metadata for the UI."""
    rows = (await db.execute(
        select(ApplicationSettings).where(ApplicationSettings.key.in_(SETTING_DEFS.keys()))
    )).scalars().all()
    stored = {r.key: r.value for r in rows}
    out = []
    for key, d in SETTING_DEFS.items():
        out.append({
            "key": key,
            "label": d["label"],
            "category": d["category"],
            "type": d["type"],
            "value": stored.get(key, d["default"]),
            "default": d["default"],
            "is_default": key not in stored,
        })
    return {"settings": out}


@router.post("/save")
async def save_setting(payload: SettingIn, db: AsyncSession = Depends(get_db),
                       user: User = Depends(get_current_user),
                       _: None = Depends(require_csrf)):
    ok, value = _validate_value(payload.key, payload.value)
    if not ok:
        raise HTTPException(status_code=400, detail=value)
    row = await db.execute(
        select(ApplicationSettings).where(ApplicationSettings.key == payload.key))
    row = row.scalar()
    if row:
        row.value = value
        row.description = payload.description or row.description
    else:
        db.add(ApplicationSettings(key=payload.key, value=value,
                                   description=payload.description or SETTING_DEFS[payload.key]["label"],
                                   category=SETTING_DEFS[payload.key]["category"]))
    await db.commit()
    await log_audit(db, user.id, "settings.save", "application_setting", None,
                    {"key": payload.key})
    return {"key": payload.key, "value": value}


@router.post("/save-batch")
async def save_batch(payload: BatchSettingsIn, db: AsyncSession = Depends(get_db),
                     user: User = Depends(get_current_user),
                     _: None = Depends(require_csrf)):
    """Save multiple settings at once (Settings page form)."""
    applied, errors = {}, {}
    for key, value in payload.values.items():
        if key not in SETTING_DEFS:
            errors[key] = "Unknown setting key"
            continue
        ok, v = _validate_value(key, value)
        if not ok:
            errors[key] = v
            continue
        row = await db.execute(
            select(ApplicationSettings).where(ApplicationSettings.key == key))
        row = row.scalar()
        if row:
            row.value = v
        else:
            db.add(ApplicationSettings(key=key, value=v,
                                       description=SETTING_DEFS[key]["label"],
                                       category=SETTING_DEFS[key]["category"]))
        applied[key] = v
    await db.commit()
    if applied:
        await log_audit(db, user.id, "settings.save_batch", "application_setting", None,
                        {"keys": list(applied.keys())})
    return {"applied": applied, "errors": errors}


@router.post("/reset")
async def reset_setting(payload: SettingIn, db: AsyncSession = Depends(get_db),
                        user: User = Depends(get_current_user),
                        _: None = Depends(require_csrf)):
    """Reset one setting to its default value (deletes the DB row)."""
    if payload.key not in SETTING_DEFS:
        raise HTTPException(status_code=400, detail="Unknown setting key")
    row = await db.execute(
        select(ApplicationSettings).where(ApplicationSettings.key == payload.key))
    row = row.scalar()
    if row:
        await db.delete(row)
        await db.commit()
    await log_audit(db, user.id, "settings.reset", "application_setting", None,
                    {"key": payload.key})
    return {"key": payload.key, "value": SETTING_DEFS[payload.key]["default"]}


@router.post("/reset-all")
async def reset_all_settings(db: AsyncSession = Depends(get_db),
                             user: User = Depends(get_current_user),
                             _: None = Depends(require_csrf)):
    rows = (await db.execute(
        select(ApplicationSettings).where(ApplicationSettings.key.in_(SETTING_DEFS.keys()))
    )).scalars().all()
    for r in rows:
        await db.delete(r)
    await db.commit()
    await log_audit(db, user.id, "settings.reset_all", "application_setting", None,
                    {"reset": len(rows)})
    return {"reset": len(rows)}
