"""FastAPI web app: create inboxes, view them, poll for new mail.

Run:  python -m uvicorn app:app --host 127.0.0.1 --port 8000 --reload
"""
import secrets
import string
from datetime import datetime, timedelta

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates

import config
import db

app = FastAPI(title="Temp Mail")
templates = Jinja2Templates(directory="templates")

_ALPHABET = string.ascii_lowercase + string.digits


def _rand(n: int) -> str:
    return "".join(secrets.choice(_ALPHABET) for _ in range(n))


def _new_inbox():
    """Create a fresh unique address + token and store it."""
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            for _ in range(20):  # retry on the tiny chance of a collision
                local = _rand(10)
                address = f"{local}@{config.MAIL_DOMAIN}"
                token = secrets.token_urlsafe(24)
                expires = datetime.now() + timedelta(minutes=config.INBOX_TTL_MINUTES)
                try:
                    cur.execute(
                        "INSERT INTO inboxes (address, token, expires_at) "
                        "VALUES (%s, %s, %s)",
                        (address, token, expires),
                    )
                    return {"address": address, "token": token,
                            "expires_at": expires.isoformat()}
                except Exception:
                    continue
        raise HTTPException(500, "could not allocate an address")
    finally:
        conn.close()


def _inbox_by_token(token: str):
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            cur.execute("SELECT * FROM inboxes WHERE token=%s", (token,))
            return cur.fetchone()
    finally:
        conn.close()


# ---------------------------------------------------------------- pages ------
@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    """Landing page. Auto-creates an inbox and redirects to it."""
    inbox = _new_inbox()
    return RedirectResponse(url=f"/inbox/{inbox['token']}", status_code=303)


@app.get("/inbox/{token}", response_class=HTMLResponse)
def inbox_page(request: Request, token: str):
    inbox = _inbox_by_token(token)
    if not inbox:
        raise HTTPException(404, "inbox not found or expired")
    return templates.TemplateResponse(
        request,
        "inbox.html",
        {
            "address": inbox["address"],
            "token": token,
            "expires_at": inbox["expires_at"].isoformat(),
        },
    )


# ------------------------------------------------------------------ api ------
@app.post("/api/inbox")
def api_new_inbox():
    """Programmatically create an inbox (for use from your own project)."""
    return _new_inbox()


@app.get("/api/inbox/{token}/messages")
def api_messages(token: str):
    inbox = _inbox_by_token(token)
    if not inbox:
        raise HTTPException(404, "inbox not found")
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            cur.execute(
                "SELECT id, from_addr, subject, received_at, seen "
                "FROM emails WHERE inbox_id=%s ORDER BY received_at DESC",
                (inbox["id"],),
            )
            rows = cur.fetchall()
    finally:
        conn.close()
    for r in rows:
        r["received_at"] = r["received_at"].isoformat()
    return {"address": inbox["address"], "messages": rows}


@app.delete("/api/inbox/{token}")
def api_delete_inbox(token: str):
    """Delete an inbox and all its messages (cascade)."""
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            cur.execute("DELETE FROM inboxes WHERE token=%s", (token,))
            deleted = cur.rowcount
    finally:
        conn.close()
    return {"deleted": deleted}


@app.post("/api/inbox/{token}/empty")
def api_empty_inbox(token: str):
    """Delete all messages in an inbox but keep the address."""
    inbox = _inbox_by_token(token)
    if not inbox:
        raise HTTPException(404, "inbox not found")
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            cur.execute("DELETE FROM emails WHERE inbox_id=%s", (inbox["id"],))
            deleted = cur.rowcount
    finally:
        conn.close()
    return {"deleted": deleted}


@app.get("/api/message/{msg_id}")
def api_message(msg_id: int, token: str):
    inbox = _inbox_by_token(token)
    if not inbox:
        raise HTTPException(404, "inbox not found")
    conn = db.get_conn()
    try:
        with conn.cursor() as cur:
            cur.execute(
                "SELECT * FROM emails WHERE id=%s AND inbox_id=%s",
                (msg_id, inbox["id"]),
            )
            msg = cur.fetchone()
            if not msg:
                raise HTTPException(404, "message not found")
            cur.execute("UPDATE emails SET seen=1 WHERE id=%s", (msg_id,))
    finally:
        conn.close()
    msg["received_at"] = msg["received_at"].isoformat()
    return msg


@app.on_event("startup")
def _startup():
    db.init_db()
