from __future__ import annotations

import csv
import io
import os
import secrets
import smtplib
import sqlite3
from email.message import EmailMessage
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import qrcode
import requests
from dotenv import load_dotenv
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
from PIL import Image, ImageDraw
from pydantic import BaseModel, Field
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException

DB_PATH = Path(__file__).with_name("wedding.db")
load_dotenv(Path(__file__).with_name(".env"))

BASE_URL = os.getenv("PUBLIC_BASE_URL", "http://localhost:5173").rstrip("/")
CORS_ORIGINS = [
    origin.strip()
    for origin in os.getenv("CORS_ORIGINS", "http://localhost:5173,http://127.0.0.1:5173").split(",")
    if origin.strip()
]
TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "")
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "")
TWILIO_WHATSAPP_FROM = os.getenv("TWILIO_WHATSAPP_FROM", "")
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
GROQ_MODEL = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
SMTP_HOST = os.getenv("SMTP_HOST", "")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USERNAME = os.getenv("SMTP_USERNAME", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
EMAIL_FROM = os.getenv("EMAIL_FROM", SMTP_USERNAME)
EVENT = {
    "couple": "Alex & Alyssa",
    "title": "Traditional Igbo Wedding Ceremony",
    "date": "Friday, 19th June 2026",
    "time": "To be communicated",
    "venue": "Vintano Hotel Hall, Lekki Phase 1, Lagos",
    "theme": "A curated Igbo cultural celebration",
    "dress_code": "Classic Igbo elegance: George wrappers, lace fabrics, coral beads, Isiagu and refined traditional fits",
    "rsvp_deadline": "Friday, 5th June 2026",
    "palette": "Shades of champagne, burnt orange, gold, burgundy, red and pink",
    "story": (
        "Nnamdi Alexander Onuoha and Alyssa Lynn Onuoha (née Cuffie) invite you to witness "
        "a deeply rooted Igbo cultural celebration thoughtfully curated to honor heritage "
        "while delivering a refined and unforgettable experience for guests traveling from across the world. "
        "The vision blends tradition, elegance, and modern luxury through meaningful, visually timeless moments."
    ),
}

app = FastAPI(title="Traditional Wedding RSVP Accreditation API")

# WSGI entry point for Namecheap / cPanel Passenger
application = app
app.add_middleware(
    CORSMiddleware,
    allow_origins=CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


def db() -> sqlite3.Connection:
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def make_token() -> str:
    return secrets.token_urlsafe(6).replace("-", "").replace("_", "").upper()[:8]


def row_to_guest(row: sqlite3.Row) -> dict[str, Any]:
    data = dict(row)
    data["plus_one_allowed"] = bool(data["plus_one_allowed"])
    data["vip"] = bool(data["vip"])
    data["qr_url"] = f"/media/access/{data['token']}.gif" if data["rsvp_status"] == "accepted" else None
    data["invite_url"] = f"{BASE_URL}/rsvp/{data['token']}"
    return data


def init_db() -> None:
    with db() as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS guests (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                token TEXT UNIQUE NOT NULL,
                name TEXT NOT NULL,
                phone TEXT,
                email TEXT,
                family_side TEXT,
                vip INTEGER NOT NULL DEFAULT 0,
                table_assignment TEXT,
                plus_one_allowed INTEGER NOT NULL DEFAULT 0,
                rsvp_status TEXT NOT NULL DEFAULT 'pending',
                attendee_count INTEGER,
                notes TEXT,
                checked_in_at TEXT,
                invitation_sent_at TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        count = conn.execute("SELECT COUNT(*) AS total FROM guests").fetchone()["total"]
        if count:
            return
        seed_guests = [
            ("W8BJTMBK", "Ugo Ebeniro", "+2348012345678", "ugo@example.com", "Friends", 0, "Table 8", 1),
            ("ADE40VIP", "Chief Ifeanyi Nwosu", "+2348022222222", "ifeanyi@example.com", "Bride Family", 1, "VIP 1", 1),
            ("MORAYO26", "Adaeze Obi", "+2348033333333", "adaeze@example.com", "Groom Family", 0, "Table 3", 0),
        ]
        conn.executemany(
            """
            INSERT INTO guests (
                token, name, phone, email, family_side, vip, table_assignment,
                plus_one_allowed, created_at
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            [(*guest, now_iso()) for guest in seed_guests],
        )


@app.on_event("startup")
def startup() -> None:
    init_db()
    refresh_demo_guest_names()


def refresh_demo_guest_names() -> None:
    """Keep local seeded data aligned with the Igbo event brief."""
    with db() as conn:
        conn.execute(
            """
            UPDATE guests
            SET name = 'Ugo Ebeniro', email = 'ugo@example.com'
            WHERE token = 'W8BJTMBK'
            """
        )
        conn.execute(
            """
            UPDATE guests
            SET name = 'Chief Ifeanyi Nwosu', email = 'ifeanyi@example.com'
            WHERE token = 'ADE40VIP'
            """
        )
        conn.execute(
            """
            UPDATE guests
            SET name = 'Adaeze Obi', email = 'adaeze@example.com'
            WHERE token = 'MORAYO26'
            """
        )


class GuestCreate(BaseModel):
    name: str
    phone: str | None = None
    email: str | None = None
    family_side: str | None = None
    vip: bool = False
    table_assignment: str | None = None
    plus_one_allowed: bool = False


class RsvpUpdate(BaseModel):
    status: str = Field(pattern="^(accepted|declined)$")
    attendee_count: int | None = Field(default=None, ge=1, le=10)
    notes: str | None = None


class MessageSend(BaseModel):
    channel: str = Field(pattern="^(whatsapp|email|both)$")
    use_ai: bool = False
    body: str | None = None
    subject: str | None = None


class BulkMessageSend(MessageSend):
    tokens: list[str] | None = None
    rsvp_status: str = Field(default="pending", pattern="^(all|pending|accepted|declined)$")


class AiDraftRequest(BaseModel):
    token: str | None = None
    channel: str = Field(default="whatsapp", pattern="^(whatsapp|email)$")
    tone: str = "warm, premium and culturally respectful"
    purpose: str = "invitation"


class ChatMessage(BaseModel):
    role: str = Field(pattern="^(user|assistant|system)$")
    content: str


class ChatRequest(BaseModel):
    messages: list[ChatMessage]
    include_guest_context: bool = True


@app.get("/api/event")
def get_event() -> dict[str, Any]:
    return EVENT


@app.get("/api/guests")
def list_guests(search: str | None = None) -> dict[str, Any]:
    query = "SELECT * FROM guests"
    params: list[Any] = []
    if search:
        query += " WHERE name LIKE ? OR phone LIKE ? OR email LIKE ? OR token LIKE ?"
        term = f"%{search}%"
        params.extend([term, term, term, term])
    query += " ORDER BY vip DESC, name ASC"
    with db() as conn:
        guests = [row_to_guest(row) for row in conn.execute(query, params).fetchall()]
    return {"guests": guests}


@app.post("/api/guests")
def create_guest(payload: GuestCreate) -> dict[str, Any]:
    token = make_token()
    with db() as conn:
        conn.execute(
            """
            INSERT INTO guests (
                token, name, phone, email, family_side, vip, table_assignment,
                plus_one_allowed, created_at
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                token,
                payload.name,
                payload.phone,
                payload.email,
                payload.family_side,
                int(payload.vip),
                payload.table_assignment,
                int(payload.plus_one_allowed),
                now_iso(),
            ),
        )
        guest = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    return {"guest": row_to_guest(guest)}


@app.post("/api/guests/upload")
async def upload_guests(file: UploadFile = File(...)) -> dict[str, Any]:
    content = (await file.read()).decode("utf-8-sig")
    reader = csv.DictReader(io.StringIO(content))
    imported = 0
    with db() as conn:
        for row in reader:
            name = (row.get("name") or row.get("guest name") or "").strip()
            if not name:
                continue
            conn.execute(
                """
                INSERT INTO guests (
                    token, name, phone, email, family_side, vip, table_assignment,
                    plus_one_allowed, created_at
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    make_token(),
                    name,
                    row.get("phone"),
                    row.get("email"),
                    row.get("family_side") or row.get("family side"),
                    int(str(row.get("vip", "")).lower() in {"1", "true", "yes", "vip"}),
                    row.get("table_assignment") or row.get("table assignment"),
                    int(str(row.get("plus_one_allowed", "")).lower() in {"1", "true", "yes"}),
                    now_iso(),
                ),
            )
            imported += 1
    return {"imported": imported}


@app.put("/api/guests/{token}")
def update_guest(token: str, payload: GuestCreate) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Guest not found")
        conn.execute(
            """
            UPDATE guests
            SET name = ?, phone = ?, email = ?, family_side = ?, vip = ?,
                table_assignment = ?, plus_one_allowed = ?
            WHERE token = ?
            """,
            (
                payload.name,
                payload.phone,
                payload.email,
                payload.family_side,
                int(payload.vip),
                payload.table_assignment,
                int(payload.plus_one_allowed),
                token,
            ),
        )
        updated = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    return {"guest": row_to_guest(updated)}


@app.delete("/api/guests/{token}")
def delete_guest(token: str) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Guest not found")
        conn.execute("DELETE FROM guests WHERE token = ?", (token,))
    return {"deleted": token}


@app.get("/api/guests/export")
def export_guests() -> Response:
    with db() as conn:
        rows = conn.execute(
            "SELECT token, name, phone, email, family_side, vip, table_assignment, "
            "plus_one_allowed, rsvp_status, attendee_count, notes, checked_in_at, "
            "invitation_sent_at, created_at FROM guests ORDER BY name"
        ).fetchall()

    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(["Token", "Name", "Phone", "Email", "Family Side", "VIP",
                      "Table", "+1 Allowed", "RSVP", "Attendees", "Notes",
                      "Checked In", "Invitation Sent", "Created"])
    for r in rows:
        writer.writerow([r[c] for c in [
            "token", "name", "phone", "email", "family_side", "vip",
            "table_assignment", "plus_one_allowed", "rsvp_status", "attendee_count",
            "notes", "checked_in_at", "invitation_sent_at", "created_at"
        ]])
    return Response(
        output.getvalue(),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=guests.csv"},
    )


@app.get("/api/rsvp/{token}")
def get_rsvp(token: str) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Invalid invitation link")
    return {"event": EVENT, "guest": row_to_guest(row)}


@app.post("/api/rsvp/{token}")
def update_rsvp(token: str, payload: RsvpUpdate) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Invalid invitation link")
        attendee_count = 1
        if payload.status == "accepted":
            max_attendees = 2 if row["plus_one_allowed"] else 1
            attendee_count = payload.attendee_count or 1
            if attendee_count > max_attendees:
                raise HTTPException(status_code=400, detail="Plus-one is not enabled for this invitation")
        else:
            attendee_count = 0
        conn.execute(
            """
            UPDATE guests
            SET rsvp_status = ?, attendee_count = ?, notes = ?
            WHERE token = ?
            """,
            (payload.status, attendee_count, payload.notes, token),
        )
        updated = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    return {
        "guest": row_to_guest(updated),
        "message": confirmation_message(row_to_guest(updated)) if payload.status == "accepted" else None,
    }


def confirmation_message(guest: dict[str, Any]) -> str:
    return (
        f"Dear {guest['name']},\n\n"
        "Thank you for confirming your attendance.\n\n"
        "Please find your QR code for event access below:\n"
        f"{BASE_URL}{guest['qr_url']}\n\n"
        "Kindly present this QR code at the venue for accreditation.\n\n"
        "We look forward to celebrating with you."
    )


def invitation_message(guest: dict[str, Any]) -> str:
    return (
        f"Dear {guest['name']},\n\n"
        f"You are specially invited to celebrate with us at our {EVENT['title']}.\n\n"
        f"Date: {EVENT['date']}\n"
        f"Time: {EVENT['time']}\n"
        f"Venue: {EVENT['venue']}\n\n"
        f"Please verify your invitation and confirm your attendance using the link below:\n{guest['invite_url']}\n\n"
        "We look forward to celebrating with you."
    )


def groq_ready() -> bool:
    return bool(GROQ_API_KEY) and not GROQ_API_KEY.startswith("replace_")


def smtp_ready() -> bool:
    values = [SMTP_HOST, SMTP_USERNAME, SMTP_PASSWORD, EMAIL_FROM]
    return all(values) and not any(value.startswith("replace_") for value in values)


def groq_draft(guest: dict[str, Any] | None, channel: str, tone: str, purpose: str) -> str:
    fallback_guest = guest or {"name": "our honoured guest", "invite_url": f"{BASE_URL}/rsvp/W8BJTMBK"}
    if not groq_ready():
        return invitation_message(fallback_guest)

    prompt = (
        f"Write a concise {channel} {purpose} message for {fallback_guest['name']}.\n"
        f"Tone: {tone}.\n"
        f"Event: {EVENT['couple']} - {EVENT['title']}.\n"
        f"Date: {EVENT['date']}.\n"
        f"Time: {EVENT['time']}.\n"
        f"Venue: {EVENT['venue']}.\n"
        f"Theme: {EVENT['theme']}.\n"
        f"Palette: {EVENT['palette']}.\n"
        f"Dress code: {EVENT['dress_code']}.\n"
        f"Creative brief: {EVENT['story']}.\n"
        f"RSVP link: {fallback_guest['invite_url']}.\n"
        "Keep it elegant, culturally respectful to Igbo heritage, and ready to send. "
        "Do not invent extra venue, time, or payment details."
    )
    try:
        response = requests.post(
            "https://api.groq.com/openai/v1/chat/completions",
            headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
            json={
                "model": GROQ_MODEL,
                "messages": [
                    {
                        "role": "system",
                        "content": (
                            "You write polished wedding operations messages for Nigerian events. "
                            "This couple is Igbo; avoid Yoruba-specific language, attire, and references."
                        ),
                    },
                    {"role": "user", "content": prompt},
                ],
                "temperature": 0.7,
                "max_tokens": 320,
            },
            timeout=20,
        )
        response.raise_for_status()
        return response.json()["choices"][0]["message"]["content"].strip()
    except requests.RequestException as exc:
        raise HTTPException(status_code=502, detail=f"Groq request failed: {exc}") from exc


def twilio_ready() -> bool:
    values = [TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_FROM]
    return all(values) and not any(value.startswith("replace_") for value in values)


def send_whatsapp(to_phone: str | None, body: str) -> str | None:
    if not to_phone:
        raise HTTPException(status_code=400, detail="Guest does not have a phone number")
    if not to_phone.startswith("+"):
        raise HTTPException(
            status_code=400,
            detail=f"Phone number '{to_phone}' is not in E.164 format (must start with +). "
                   "Example: +2348012345678",
        )
    if not twilio_ready():
        return None
    try:
        message = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN).messages.create(
            from_=TWILIO_WHATSAPP_FROM,
            to=f"whatsapp:{to_phone}",
            body=body,
        )
        return message.sid
    except TwilioRestException as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Twilio error {exc.code}: {exc.msg}",
        ) from exc


def send_email(to_email: str | None, subject: str, body: str) -> str | None:
    if not to_email:
        raise HTTPException(status_code=400, detail="Guest does not have an email address")
    if not smtp_ready():
        return None
    msg = EmailMessage()
    msg["Subject"] = subject
    msg["From"] = EMAIL_FROM
    msg["To"] = to_email
    msg.set_content(body)
    try:
        if SMTP_PORT == 465:
            # Implicit TLS (SSL) — required by cPanel hosts on port 465
            with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=20) as server:
                server.login(SMTP_USERNAME, SMTP_PASSWORD)
                server.send_message(msg)
        else:
            # Explicit TLS (STARTTLS) — standard for port 587
            with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=20) as server:
                server.starttls()
                server.login(SMTP_USERNAME, SMTP_PASSWORD)
                server.send_message(msg)
    except smtplib.SMTPException as exc:
        raise HTTPException(status_code=502, detail=f"SMTP error: {exc}") from exc
    return "sent"


@app.post("/api/ai/draft")
def ai_draft(payload: AiDraftRequest) -> dict[str, Any]:
    guest = None
    if payload.token:
        with db() as conn:
            row = conn.execute("SELECT * FROM guests WHERE token = ?", (payload.token,)).fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Guest not found")
        guest = row_to_guest(row)
    body = groq_draft(guest, payload.channel, payload.tone, payload.purpose)
    return {
        "status": "ai" if groq_ready() else "preview",
        "body": body,
        "subject": f"Invitation to {EVENT['couple']}'s Traditional Wedding",
    }


@app.post("/api/ai/chat")
def ai_chat(payload: ChatRequest) -> dict[str, Any]:
    if not groq_ready():
        return {
            "status": "preview",
            "reply": "Groq API key not configured. Add your key in backend/.env to enable the AI assistant.",
        }

    system_parts = [
        f"You are the wedding operations AI assistant for {EVENT['couple']}'s {EVENT['title']}.",
        f"Date: {EVENT['date']} | Venue: {EVENT['venue']} | Theme: {EVENT['theme']}",
        f"Dress code: {EVENT['dress_code']}",
        f"RSVP deadline: {EVENT['rsvp_deadline']}",
        "The couple is Igbo. Avoid Yoruba-specific references.",
        "You can help with: drafting messages, analyzing the guest list, suggesting groupings, table assignments, and wedding operations tasks.",
        "Be concise, warm, and professional.",
    ]

    if payload.include_guest_context:
        with db() as conn:
            rows = conn.execute(
                "SELECT name, phone, email, family_side, vip, table_assignment, "
                "plus_one_allowed, rsvp_status, attendee_count, notes, token "
                "FROM guests ORDER BY vip DESC, name ASC"
            ).fetchall()
        guests_summary = "\n".join(
            f"- {r['name']} | side: {r['family_side'] or '-'} | status: {r['rsvp_status']} | "
            f"table: {r['table_assignment'] or '-'} | VIP: {'yes' if r['vip'] else 'no'} | "
            f"phone: {r['phone'] or '-'} | email: {r['email'] or '-'}"
            for r in rows
        )
        system_parts.append(f"\nCurrent guest list ({len(rows)} total):\n{guests_summary}")

    system_prompt = "\n".join(system_parts)

    try:
        response = requests.post(
            "https://api.groq.com/openai/v1/chat/completions",
            headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
            json={
                "model": GROQ_MODEL,
                "messages": [
                    {"role": "system", "content": system_prompt},
                    *[{"role": m.role, "content": m.content} for m in payload.messages],
                ],
                "temperature": 0.7,
                "max_tokens": 600,
            },
            timeout=30,
        )
        response.raise_for_status()
        reply = response.json()["choices"][0]["message"]["content"].strip()
    except requests.RequestException as exc:
        raise HTTPException(status_code=502, detail=f"Groq request failed: {exc}") from exc

    return {"status": "ai", "reply": reply}


@app.post("/api/ai/group-guests")
def ai_group_guests() -> dict[str, Any]:
    if not groq_ready():
        return {"status": "preview", "groups": [], "message": "Add Groq key for AI-powered grouping."}

    with db() as conn:
        rows = conn.execute("SELECT name, family_side, rsvp_status, table_assignment, vip FROM guests ORDER BY name").fetchall()

    guest_names = [r["name"] for r in rows]
    prompt = (
        f"Here are {len(rows)} guests for {EVENT['couple']}'s wedding:\n"
        + "\n".join(f"- {r['name']} | side: {r['family_side'] or '-'} | status: {r['rsvp_status']} | table: {r['table_assignment'] or '-'}" for r in rows)
        + "\n\nSuggest how to group these guests neatly for the admin dashboard (e.g. by family side, by table, or by RSVP status). "
        "Return a JSON object with a 'groups' array where each group has a 'name' and a 'guests' array of guest names. "
        "Keep groups balanced and sensible. Max 6 groups."
    )

    try:
        response = requests.post(
            "https://api.groq.com/openai/v1/chat/completions",
            headers={"Authorization": f"Bearer {GROQ_API_KEY}", "Content-Type": "application/json"},
            json={
                "model": GROQ_MODEL,
                "messages": [
                    {"role": "system", "content": "You output only valid JSON. No markdown, no explanation."},
                    {"role": "user", "content": prompt},
                ],
                "temperature": 0.3,
                "max_tokens": 800,
                "response_format": {"type": "json_object"},
            },
            timeout=20,
        )
        response.raise_for_status()
        data = response.json()["choices"][0]["message"]["content"].strip()
        import json
        result = json.loads(data)
    except Exception as exc:
        return {"status": "error", "groups": [], "message": str(exc)}

    return {"status": "ai", "groups": result.get("groups", []), "message": "Guests grouped by AI"}


@app.post("/api/invitations/{token}/resend")
def resend_invitation(token: str) -> dict[str, Any]:
    return send_invitation(token, MessageSend(channel="whatsapp"))


@app.post("/api/invitations/{token}/send")
def send_invitation(token: str, payload: MessageSend) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Guest not found")
    guest = row_to_guest(row)
    message = build_invitation_body(guest, payload)
    subject = payload.subject or f"Invitation to {EVENT['couple']}'s Traditional Wedding"
    result = deliver_message(guest, payload.channel, subject, message)
    sent = bool(result["whatsapp_sid"] or result["email"])
    if sent:
        with db() as conn:
            conn.execute("UPDATE guests SET invitation_sent_at = ? WHERE token = ?", (now_iso(), token))
    return {
        "status": "sent" if sent else "preview",
        "delivery": result,
        "subject": subject,
        "whatsapp": message,
    }


def build_invitation_body(guest: dict[str, Any], payload: MessageSend) -> str:
    if payload.body:
        return payload.body
    if payload.use_ai:
        return groq_draft(guest, "whatsapp" if payload.channel == "both" else payload.channel, "warm, premium and culturally respectful", "invitation")
    return invitation_message(guest)


def deliver_message(guest: dict[str, Any], channel: str, subject: str, message: str) -> dict[str, Any]:
    result: dict[str, Any] = {"whatsapp_sid": None, "email": None, "errors": []}
    if channel in {"whatsapp", "both"}:
        try:
            result["whatsapp_sid"] = send_whatsapp(guest["phone"], message)
        except HTTPException as exc:
            if channel == "whatsapp":
                raise
            result["errors"].append(f"WhatsApp: {exc.detail}")
    if channel in {"email", "both"}:
        try:
            result["email"] = send_email(guest["email"], subject, message)
        except HTTPException as exc:
            if channel == "email":
                raise
            result["errors"].append(f"Email: {exc.detail}")
    return result


@app.post("/api/invitations/send-bulk")
def send_bulk_invitations(payload: BulkMessageSend) -> dict[str, Any]:
    query = "SELECT * FROM guests"
    params: list[Any] = []
    filters: list[str] = []
    if payload.tokens:
        placeholders = ",".join("?" for _ in payload.tokens)
        filters.append(f"token IN ({placeholders})")
        params.extend(payload.tokens)
    elif payload.rsvp_status != "all":
        filters.append("rsvp_status = ?")
        params.append(payload.rsvp_status)
    if filters:
        query += " WHERE " + " AND ".join(filters)
    query += " ORDER BY vip DESC, name ASC"

    with db() as conn:
        rows = conn.execute(query, params).fetchall()

    subject = payload.subject or f"Invitation to {EVENT['couple']}'s Traditional Wedding"
    results = []
    for row in rows:
        guest = row_to_guest(row)
        message = build_invitation_body(guest, payload)
        delivery = deliver_message(guest, payload.channel, subject, message)
        sent = bool(delivery["whatsapp_sid"] or delivery["email"])
        if sent:
            with db() as conn:
                conn.execute("UPDATE guests SET invitation_sent_at = ? WHERE token = ?", (now_iso(), guest["token"]))
        results.append(
            {
                "token": guest["token"],
                "name": guest["name"],
                "status": "sent" if sent else "preview",
                "delivery": delivery,
            }
        )

    sent_count = sum(1 for item in results if item["status"] == "sent")
    return {
        "status": "sent" if sent_count else "preview",
        "total": len(results),
        "sent": sent_count,
        "preview": len(results) - sent_count,
        "results": results[:25],
    }


@app.get("/api/dashboard")
def dashboard() -> dict[str, Any]:
    with db() as conn:
        rows = conn.execute("SELECT rsvp_status, COUNT(*) AS total FROM guests GROUP BY rsvp_status").fetchall()
        checked_in = conn.execute("SELECT COUNT(*) AS total FROM guests WHERE checked_in_at IS NOT NULL").fetchone()[
            "total"
        ]
        total = conn.execute("SELECT COUNT(*) AS total FROM guests").fetchone()["total"]
    stats = {"accepted": 0, "pending": 0, "declined": 0}
    stats.update({row["rsvp_status"]: row["total"] for row in rows})
    stats["checked_in"] = checked_in
    stats["total"] = total
    return {
        "stats": stats,
        "services": {
            "groq": groq_ready(),
            "whatsapp": twilio_ready(),
            "email": smtp_ready(),
        },
    }


def guest_scan_url(token: str) -> str:
    return f"{BASE_URL}/scan?token={token}"


def get_accepted_guest(token: str) -> sqlite3.Row:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Invalid QR token")
    if row["rsvp_status"] != "accepted":
        raise HTTPException(status_code=403, detail="QR code is available only after RSVP acceptance")
    return row


@app.get("/media/access/{token}.png")
def qr_code(token: str) -> Response:
    get_accepted_guest(token)
    scan_url = guest_scan_url(token)
    image = qrcode.make(scan_url)
    output = io.BytesIO()
    image.save(output, format="PNG")
    return Response(output.getvalue(), media_type="image/png")


@app.get("/media/access/{token}.gif")
def animated_qr_code(token: str) -> Response:
    row = get_accepted_guest(token)
    scan_url = guest_scan_url(token)
    qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H, box_size=9, border=2)
    qr.add_data(scan_url)
    qr.make(fit=True)
    qr_image = qr.make_image(fill_color="#17120d", back_color="#ffffff").convert("RGBA")
    qr_image = qr_image.resize((300, 300), Image.Resampling.NEAREST)

    frames = []
    palette = ["#f3dfbc", "#c89b3c", "#d96745", "#7a1731", "#d95780", "#c89b3c"]
    for idx, color in enumerate(palette):
        frame = Image.new("RGBA", (390, 430), "#fffaf1")
        draw = ImageDraw.Draw(frame)
        inset = 18 + (idx % 3) * 3
        draw.rounded_rectangle((inset, inset, 390 - inset, 390 - inset), radius=28, outline=color, width=8)
        for bead in range(12):
            x = 32 + bead * 29
            bead_color = palette[(idx + bead) % len(palette)]
            draw.ellipse((x, 12, x + 10, 22), fill=bead_color)
            draw.ellipse((x, 368, x + 10, 378), fill=bead_color)
        draw.rounded_rectangle((30, 30, 360, 360), radius=24, fill="#ffffff", outline="#eadcc9", width=2)
        frame.alpha_composite(qr_image, (45, 45))
        scan_y = 62 + idx * 42
        draw.rounded_rectangle((56, scan_y, 334, scan_y + 10), radius=5, fill=(200, 155, 60, 72))
        draw.text((195, 378), EVENT["couple"].upper(), fill="#17120d", anchor="mm")
        draw.text((195, 402), f"ACCESS PASS - {row['token']}", fill="#a0917d", anchor="mm")
        frames.append(frame.convert("P", palette=Image.Palette.ADAPTIVE))

    output = io.BytesIO()
    frames[0].save(output, format="GIF", save_all=True, append_images=frames[1:], duration=260, loop=0)
    return Response(output.getvalue(), media_type="image/gif")


@app.post("/api/accreditation/{token}")
def accredit(token: str) -> dict[str, Any]:
    with db() as conn:
        row = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
        if not row:
            return {"status": "invalid", "title": "Invalid QR Code", "guest": None}
        if row["rsvp_status"] != "accepted":
            return {"status": "invalid", "title": "RSVP Not Accepted", "guest": row_to_guest(row)}
        if row["checked_in_at"]:
            return {"status": "duplicate", "title": "Already Checked In", "guest": row_to_guest(row)}
        conn.execute("UPDATE guests SET checked_in_at = ? WHERE token = ?", (now_iso(), token))
        updated = conn.execute("SELECT * FROM guests WHERE token = ?", (token,)).fetchone()
    return {"status": "valid", "title": "Access Granted", "guest": row_to_guest(updated)}
