"""Sign-in, sign-out, invites."""

from __future__ import annotations

import secrets
from datetime import UTC, datetime, timedelta

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from psycopg import Connection
from pydantic import BaseModel, Field

from ..auth import (
    User,
    check_throttle,
    clear_throttle,
    create_session,
    current_user,
    destroy_session,
    hash_password,
    load_memberships,
    me_payload,
    record_failure,
    require_admin,
    require_center,
    token_hash,
    verify_password,
)
from ..db import fetchall, fetchone, get_conn
from ..settings import get_settings

router = APIRouter(tags=["auth"])
INVITE_DAYS = 14


class LoginIn(BaseModel):
    email: str
    password: str


class InviteIn(BaseModel):
    email: str = Field(min_length=3, max_length=254)
    center_id: int | None = None
    role: str | None = Field(default="forecaster", pattern="^(center_admin|forecaster)$")
    is_admin: bool = False


class AcceptIn(BaseModel):
    token: str
    name: str = Field(min_length=1, max_length=120)
    password: str


def _user(conn: Connection, user_id: int, csrf: str) -> User:
    row = fetchone(conn, "SELECT id, email, name, is_admin FROM users WHERE id = %s", (user_id,))
    return User(**row, csrf=csrf, memberships=load_memberships(conn, user_id))


@router.post("/auth/login")
def login(body: LoginIn, request: Request, response: Response, conn: Connection = Depends(get_conn)):
    key = f"{request.client.host if request.client else '?'}:{body.email.strip().lower()}"
    check_throttle(key)
    user = fetchone(
        conn, "SELECT id, password_hash FROM users WHERE email = %s AND is_active", (body.email.strip(),)
    )
    if not user or not user["password_hash"] or not verify_password(user["password_hash"], body.password):
        record_failure(key)
        raise HTTPException(401, "Incorrect email or password")
    clear_throttle(key)
    csrf = create_session(conn, user["id"], response)
    return me_payload(conn, _user(conn, user["id"], csrf))


@router.post("/auth/logout")
def logout(request: Request, response: Response, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    destroy_session(conn, request, response)
    return {"ok": True}


@router.get("/auth/me")
def me(conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    return me_payload(conn, user)


@router.post("/invites")
def create_invite(body: InviteIn, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    if body.is_admin:
        require_admin(user)
    elif body.center_id is None:
        raise HTTPException(422, "Choose a center for the invite")
    else:
        require_center(user, body.center_id, admin=True)
    token = secrets.token_urlsafe(32)
    expires = datetime.now(UTC) + timedelta(days=INVITE_DAYS)
    conn.execute(
        """INSERT INTO invites (token_hash, email, center_id, role, is_admin, created_by, expires_at)
           VALUES (%s, %s, %s, %s, %s, %s, %s)""",
        (token_hash(token), body.email.strip(), body.center_id, body.role if body.center_id else None, body.is_admin, user.id, expires),
    )
    return {"url": f"{get_settings().public_base_url.rstrip('/')}/invite/{token}", "expires_at": expires}


@router.get("/invites")
def list_invites(center_id: int | None = None, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    if center_id is None:
        require_admin(user)
        return fetchall(conn, "SELECT id, email, center_id, role, is_admin, created_at, expires_at, accepted_at FROM invites ORDER BY created_at DESC LIMIT 200")
    require_center(user, center_id, admin=True)
    return fetchall(
        conn,
        "SELECT id, email, center_id, role, is_admin, created_at, expires_at, accepted_at FROM invites WHERE center_id = %s ORDER BY created_at DESC",
        (center_id,),
    )


def _valid_invite(conn: Connection, token: str) -> dict:
    inv = fetchone(
        conn,
        """SELECT i.*, c.name AS center_name FROM invites i LEFT JOIN centers c ON c.id = i.center_id
           WHERE i.token_hash = %s""",
        (token_hash(token),),
    )
    if not inv or inv["accepted_at"] or inv["expires_at"] < datetime.now(UTC):
        raise HTTPException(404, "This invite link is invalid or has expired.")
    return inv


@router.get("/invites/{token}")
def get_invite(token: str, conn: Connection = Depends(get_conn)):
    inv = _valid_invite(conn, token)
    return {"email": inv["email"], "center_name": inv["center_name"], "role": inv["role"], "is_admin": inv["is_admin"]}


@router.post("/auth/accept-invite")
def accept_invite(body: AcceptIn, response: Response, conn: Connection = Depends(get_conn)):
    inv = _valid_invite(conn, body.token)
    existing = fetchone(conn, "SELECT id FROM users WHERE email = %s", (inv["email"],))
    if existing:
        user_id = existing["id"]
        conn.execute("UPDATE users SET password_hash = %s, name = %s WHERE id = %s", (hash_password(body.password), body.name, user_id))
    else:
        user_id = fetchone(
            conn,
            "INSERT INTO users (email, name, password_hash, is_admin) VALUES (%s, %s, %s, %s) RETURNING id",
            (inv["email"], body.name, hash_password(body.password), inv["is_admin"]),
        )["id"]
    if inv["is_admin"]:
        conn.execute("UPDATE users SET is_admin = true WHERE id = %s", (user_id,))
    if inv["center_id"]:
        conn.execute(
            """INSERT INTO memberships (user_id, center_id, role) VALUES (%s, %s, %s)
               ON CONFLICT (user_id, center_id) DO UPDATE SET role = EXCLUDED.role""",
            (user_id, inv["center_id"], inv["role"] or "forecaster"),
        )
    conn.execute("UPDATE invites SET accepted_at = now(), accepted_user_id = %s WHERE id = %s", (user_id, inv["id"]))
    csrf = create_session(conn, user_id, response)
    return me_payload(conn, _user(conn, user_id, csrf))
