"""Suppression list manager: manual add, CSV/TXT import, search, delete, Clear All,
bulk check. Suppressed addresses are skipped at import AND at send time."""
import csv
import io

from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile
from pydantic import BaseModel, EmailStr
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db, require_csrf
from app.core.security import log_audit
from app.models import SuppressionEntry, User
from app.services.recipient_service import is_valid_email

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

REASON_CHOICES = ("unsubscribed", "bounced", "complaint", "manual", "other")


def _norm_reason(r: str) -> str:
    r = (r or "").strip().lower()
    if r == "complained":
        r = "complaint"
    return r if r in REASON_CHOICES else "manual"


class SuppressionIn(BaseModel):
    email: EmailStr
    reason: str = "manual"
    source: str = ""


class BulkEmails(BaseModel):
    emails: list[EmailStr]
    reason: str = "manual"


class CheckEmails(BaseModel):
    emails: list[EmailStr]


def _serialize(s: SuppressionEntry) -> dict:
    return {
        "id": s.id,
        "email": s.email,
        "reason": s.reason,
        "source": s.source,
        "campaign_id": s.campaign_id,
        "created_at": s.created_at.isoformat() if s.created_at else None,
    }


@router.get("/list")
async def list_suppression(search: str = Query(""),
                           reason: str = Query(""),
                           page: int = Query(1, ge=1),
                           page_size: int = Query(100, ge=1, le=500),
                           db: AsyncSession = Depends(get_db),
                           user: User = Depends(get_current_user)):
@router.post("/import")
async def import_suppression(file: UploadFile = None,
                             reason: str = Query("manual"),
                             db: AsyncSession = Depends(get_db),
                             user: User = Depends(get_current_user),
                             _: None = Depends(require_csrf)):
    """Import suppressed emails from CSV/TXT (one email per line or email,reason)."""
    if file is None:
        raise HTTPException(status_code=400, detail="No file uploaded")
    raw = await file.read()
    if len(raw) > 2 * 1024 * 1024:
        raise HTTPException(status_code=413, detail="File exceeds 2MB")
    try:
        text = raw.decode("utf-8-sig")
    except UnicodeDecodeError:
        raise HTTPException(status_code=400, detail="File must be UTF-8 text")
    if text.lstrip().startswith(("{", "[")):
        raise HTTPException(status_code=400, detail="Only CSV or TXT files are accepted")

    entries, invalid = [], []
    lines = text.splitlines()
    looks_csv = any("," in ln for ln in lines[:50])
    if looks_csv:
        for row in csv.reader(io.StringIO(text)):
            if not row or not (row[0] or "").strip():
                continue
            email = row[0].strip().lower()
            r = (row[1].strip() if len(row) > 1 else reason) or reason
            if email.lower().startswith(("email", "address")):
                continue
            if is_valid_email(email):
                entries.append((email, _norm_reason(r)))
            else:
                invalid.append(email)
    else:
        for line in lines:
            email = line.strip().lower()
            if not email or email.lower().startswith(("email", "address", "#")):
                continue
            if is_valid_email(email):
                entries.append((email, reason))
            else:
                invalid.append(email)

    if not entries:
        raise HTTPException(status_code=400,
                            detail=f"No valid emails found. Invalid: {invalid[:10]}")

    existing = set((await db.execute(
        select(SuppressionEntry.email).where(SuppressionEntry.user_id == user.id))).scalars())
    added = 0
    for email, r in entries:
        if email in existing:
            continue
        db.add(SuppressionEntry(user_id=user.id, email=email, reason=r,
                                source="import"))
        existing.add(email)
        added += 1
    await db.commit()
    await log_audit(db, user.id, "suppression.import", "suppression", None,
                    {"added": added, "invalid": len(invalid)})
    return {"added": added, "duplicates": len(entries) - added,
            "invalid": invalid[:20], "total_valid": len(entries)}


@router.post("/bulk")
=====SETTINGS=====
            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)}
=====LOGSQ=====
        "created_at": a.created_at.isoformat() if a.created_at else None,
    }


@router.get("/list")
async def list_logs(campaign_id: int = Query(0),
                    outcome: str = Query("", description="submitted|accepted|temp_failed|perm_failed|skipped"),
                    search: str = Query("", description="email or message id fragment"),
                    page: int = Query(1, ge=1),
                    page_size: int = Query(50, ge=1, le=200),
                    db: AsyncSession = Depends(get_db),
                    user: User = Depends(get_current_user)):
    """Unified send log: SendAttempt rows joined with CampaignRecipient for context."""
    q = (select(SendAttempt, CampaignRecipient)
         .join(CampaignRecipient, SendAttempt.campaign_recipient_id == CampaignRecipient.id)
         .join(Campaign, Campaign.id == SendAttempt.campaign_id)
         .where(Campaign.user_id == user.id))
    if campaign_id:
        q = q.where(SendAttempt.campaign_id == campaign_id)
    if outcome:
        q = q.where(SendAttempt.outcome == outcome)
    if search:
        like = f"%{search}%"
        q = q.where(or_(CampaignRecipient.email.ilike(like),
                        SendAttempt.message_id.ilike(like)))
    total = (await db.execute(
        select(func.count()).select_from(q.subquery()))).scalar() or 0
    rows = (await db.execute(
        q.order_by(SendAttempt.id.desc()).offset((page - 1) * page_size).limit(page_size)
    )).all()
    out = []
    for a, cr in rows:
        d = _ser_attempt(a)
        d["email"] = cr.email
        d["recipient_status"] = cr.status
        out.append(d)
    return {"logs": out, "total": total, "page": page, "page_size": page_size}


@router.get("/events")
async def list_events(campaign_id: int = Query(0),
=====ATT_SER=====
    return None


def _serialize(att: Attachment) -> dict:
    return {
        "id": att.id,
        "name": att.name,
        "kind": att.kind,
        "filename": att.filename,
        "mime_type": att.mime_type,
        "size_bytes": att.size_bytes,
        "file_sha256": att.file_sha256,
        "description": att.description,
        "is_html_attachment": bool(att.is_html_attachment),
        "created_at": att.created_at.isoformat() if att.created_at else None,
    }


async def _get_own(db: AsyncSession, user: User, att_id: int) -> Attachment:
    att = await db.get(Attachment, att_id)
    if not att or att.user_id != user.id:
        raise HTTPException(status_code=404, detail="Attachment not found")
    return att


@router.get("/list")
