from __future__ import annotations

import datetime as dt
import os
import re
import secrets
import smtplib
import sqlite3
import urllib.parse
import uuid
from contextlib import closing
from dataclasses import dataclass
from email.message import EmailMessage
from pathlib import Path
from typing import Literal

import jwt
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from pydantic import BaseModel, Field


COOKIE_NAME = "studio_session"
MAGIC_LINK_TTL_MINUTES = 15
SESSION_TTL_DAYS = 30
EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
StudioRole = Literal["founder", "video", "design", "social", "manager", "persona"]

auth_router = APIRouter(prefix="/api/auth", tags=["auth"])

_SECRET_KEY_CACHE: str | None = None
_SECRET_KEY_WARNED = False


class RequestMagicLinkPayload(BaseModel):
    email: str = Field(..., min_length=5, max_length=320)


class RequestMagicLinkResponse(BaseModel):
    ok: bool = True
    message: str
    dev_token: str | None = None


class VerifyMagicLinkPayload(BaseModel):
    token: str = Field(..., min_length=16, max_length=512)


class OnboardingPayload(BaseModel):
    name: str = Field(..., min_length=2, max_length=120)
    role: StudioRole


class AuthUserOut(BaseModel):
    id: str
    email: str
    name: str | None = None
    role: StudioRole | None = None
    beta_access: bool
    created_at: str


class AuthUserResponse(BaseModel):
    ok: bool = True
    user: AuthUserOut


@dataclass
class SessionContext:
    session_id: str
    session_token: str
    user: AuthUserOut


def utc_now() -> dt.datetime:
    return dt.datetime.now(dt.timezone.utc)


def to_iso(value: dt.datetime) -> str:
    return value.astimezone(dt.timezone.utc).isoformat()


def get_auth_db_path() -> Path:
    return Path(os.getenv("AUTH_DB_PATH", "./auth.db"))


def get_secret_key() -> str:
    global _SECRET_KEY_CACHE, _SECRET_KEY_WARNED

    if _SECRET_KEY_CACHE:
        return _SECRET_KEY_CACHE

    secret_key = os.getenv("SECRET_KEY")
    if secret_key:
        _SECRET_KEY_CACHE = secret_key
        return secret_key

    _SECRET_KEY_CACHE = secrets.token_urlsafe(32)
    if not _SECRET_KEY_WARNED:
        print(
            "[AUTH WARNING] SECRET_KEY ausente; gerando chave efemera. "
            "As sessoes serao invalidadas a cada reinicio."
        )
        _SECRET_KEY_WARNED = True
    return _SECRET_KEY_CACHE


def is_dev_mode() -> bool:
    return os.getenv("DEV_MODE") == "1"


def parse_beta_emails() -> set[str]:
    raw = os.getenv("BETA_EMAILS", "")
    return {item.strip().lower() for item in raw.split(",") if item.strip()}


def normalize_email(email: str) -> str:
    normalized = email.strip().lower()
    if not EMAIL_REGEX.match(normalized):
        raise HTTPException(status_code=400, detail="Email invalido.")
    return normalized


def normalize_token(token: str) -> str:
    normalized = token.strip()
    if len(normalized) < 16:
        raise HTTPException(status_code=400, detail="Token invalido.")
    return normalized


def resolve_base_url(request: Request) -> str:
    explicit = (os.getenv("APP_BASE_URL") or "").strip()
    if explicit:
        return explicit.rstrip("/")

    origin = (request.headers.get("origin") or "").strip()
    if origin:
        return origin.rstrip("/")

    configured_origins = [
        item.strip()
        for item in os.getenv("CORS_ORIGINS", "").split(",")
        if item.strip()
    ]
    if configured_origins:
        return configured_origins[0].rstrip("/")

    return "http://localhost:5173"


def db_connection() -> sqlite3.Connection:
    db_path = get_auth_db_path()
    if db_path.parent and str(db_path.parent) not in {"", "."}:
        db_path.parent.mkdir(parents=True, exist_ok=True)
    connection = sqlite3.connect(db_path, check_same_thread=False)
    connection.row_factory = sqlite3.Row
    connection.execute("PRAGMA foreign_keys = ON")
    return connection


def init_auth_db() -> None:
    with closing(db_connection()) as connection:
        connection.executescript(
            """
            CREATE TABLE IF NOT EXISTS users (
                id TEXT PRIMARY KEY,
                email TEXT NOT NULL UNIQUE,
                name TEXT,
                role TEXT,
                beta_access INTEGER NOT NULL DEFAULT 1,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS magic_links (
                id TEXT PRIMARY KEY,
                email TEXT NOT NULL,
                token TEXT NOT NULL UNIQUE,
                expires_at TEXT NOT NULL,
                used_at TEXT,
                created_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS sessions (
                id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                token TEXT NOT NULL UNIQUE,
                expires_at TEXT NOT NULL,
                revoked_at TEXT,
                created_at TEXT NOT NULL,
                user_agent TEXT,
                ip TEXT,
                FOREIGN KEY(user_id) REFERENCES users(id)
            );

            CREATE INDEX IF NOT EXISTS idx_magic_links_email ON magic_links(email);
            CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
            CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
            """
        )
        connection.commit()


def send_magic_link_email(email: str, token: str, base_url: str) -> None:
    link = f"{base_url.rstrip('/')}/?token={urllib.parse.quote(token)}"
    smtp_host = (os.getenv("SMTP_HOST") or "").strip()

    if not smtp_host:
        print(f"[MOCK EMAIL] To: {email} | Link: {link}")
        return

    message = EmailMessage()
    message["Subject"] = "Seu link de acesso ao Content Studio"
    message["From"] = (
        os.getenv("SMTP_FROM_EMAIL")
        or os.getenv("SMTP_USERNAME")
        or "no-reply@contentstudio.local"
    )
    message["To"] = email
    message.set_content(
        "Seu link de acesso ao Content Studio:\n\n"
        f"{link}\n\n"
        "Este link expira em 15 minutos."
    )

    smtp_port = int(os.getenv("SMTP_PORT", "587"))
    use_tls = os.getenv("SMTP_USE_TLS", "1") != "0"
    smtp_username = os.getenv("SMTP_USERNAME")
    smtp_password = os.getenv("SMTP_PASSWORD")

    with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as smtp:
        smtp.ehlo()
        if use_tls:
            smtp.starttls()
            smtp.ehlo()
        if smtp_username and smtp_password:
            smtp.login(smtp_username, smtp_password)
        smtp.send_message(message)


def create_session_token(user_id: str, session_id: str, expires_at: dt.datetime) -> str:
    payload = {
        "sub": user_id,
        "sid": session_id,
        "type": COOKIE_NAME,
        "exp": int(expires_at.timestamp()),
    }
    return jwt.encode(payload, get_secret_key(), algorithm="HS256")


def decode_session_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, get_secret_key(), algorithms=["HS256"])
    except ExpiredSignatureError as exc:
        raise HTTPException(status_code=401, detail="Sessao expirada.") from exc
    except InvalidTokenError as exc:
        raise HTTPException(status_code=401, detail="Sessao invalida.") from exc

    if payload.get("type") != COOKIE_NAME:
        raise HTTPException(status_code=401, detail="Sessao invalida.")

    return payload


def row_to_auth_user(row: sqlite3.Row) -> AuthUserOut:
    return AuthUserOut(
        id=row["id"],
        email=row["email"],
        name=row["name"],
        role=row["role"],
        beta_access=bool(row["beta_access"]),
        created_at=row["created_at"],
    )


def set_session_cookie(response: Response, token: str) -> None:
    response.set_cookie(
        key=COOKIE_NAME,
        value=token,
        max_age=SESSION_TTL_DAYS * 24 * 60 * 60,
        httponly=True,
        secure=not is_dev_mode(),
        samesite="lax",
        path="/",
    )


def clear_session_cookie(response: Response) -> None:
    response.delete_cookie(
        key=COOKIE_NAME,
        httponly=True,
        secure=not is_dev_mode(),
        samesite="lax",
        path="/",
    )


def get_current_session(request: Request) -> SessionContext:
    session_token = request.cookies.get(COOKIE_NAME)
    if not session_token:
        raise HTTPException(status_code=401, detail="Sessao nao encontrada.")

    payload = decode_session_token(session_token)
    now_iso = to_iso(utc_now())

    with closing(db_connection()) as connection:
        row = connection.execute(
            """
            SELECT
                s.id AS session_id,
                s.user_id AS session_user_id,
                s.expires_at AS session_expires_at,
                s.revoked_at AS session_revoked_at,
                u.id,
                u.email,
                u.name,
                u.role,
                u.beta_access,
                u.created_at
            FROM sessions s
            JOIN users u ON u.id = s.user_id
            WHERE s.token = ?
            LIMIT 1
            """,
            (session_token,),
        ).fetchone()

    if row is None:
        raise HTTPException(status_code=401, detail="Sessao invalida.")
    if row["session_revoked_at"] is not None:
        raise HTTPException(status_code=401, detail="Sessao encerrada.")
    if row["session_expires_at"] <= now_iso:
        raise HTTPException(status_code=401, detail="Sessao expirada.")
    if payload.get("sub") != row["session_user_id"] or payload.get("sid") != row["session_id"]:
        raise HTTPException(status_code=401, detail="Sessao invalida.")

    return SessionContext(
        session_id=row["session_id"],
        session_token=session_token,
        user=row_to_auth_user(row),
    )


@auth_router.post(
    "/request-link",
    response_model=RequestMagicLinkResponse,
    response_model_exclude_none=True,
)
def request_magic_link(payload: RequestMagicLinkPayload, request: Request) -> RequestMagicLinkResponse:
    allowed_emails = parse_beta_emails()
    if not allowed_emails:
        raise HTTPException(status_code=500, detail="Whitelist do beta nao configurada.")

    email = normalize_email(payload.email)
    if email not in allowed_emails:
        raise HTTPException(status_code=403, detail="Acesso ao beta ainda nao liberado para este email.")

    now = utc_now()
    created_at = to_iso(now)
    expires_at = to_iso(now + dt.timedelta(minutes=MAGIC_LINK_TTL_MINUTES))
    token = secrets.token_urlsafe(24)[:32]
    base_url = resolve_base_url(request)

    with closing(db_connection()) as connection:
        connection.execute(
            """
            UPDATE magic_links
            SET used_at = ?
            WHERE email = ? AND used_at IS NULL
            """,
            (created_at, email),
        )
        connection.execute(
            """
            INSERT INTO magic_links (id, email, token, expires_at, used_at, created_at)
            VALUES (?, ?, ?, ?, NULL, ?)
            """,
            (uuid.uuid4().hex, email, token, expires_at, created_at),
        )
        connection.commit()

    send_magic_link_email(email=email, token=token, base_url=base_url)

    response = RequestMagicLinkResponse(
        ok=True,
        message="Link enviado para seu email",
    )
    if is_dev_mode():
        response.dev_token = token
    return response


@auth_router.post("/verify", response_model=AuthUserResponse)
def verify_magic_link(
    payload: VerifyMagicLinkPayload,
    request: Request,
    response: Response,
) -> AuthUserResponse:
    token = normalize_token(payload.token)
    now = utc_now()
    now_iso = to_iso(now)
    expires_at = now + dt.timedelta(days=SESSION_TTL_DAYS)

    with closing(db_connection()) as connection:
        magic_link = connection.execute(
            """
            SELECT *
            FROM magic_links
            WHERE token = ?
            LIMIT 1
            """,
            (token,),
        ).fetchone()

        if magic_link is None:
            raise HTTPException(status_code=400, detail="Link magico invalido ou expirado.")
        if magic_link["used_at"] is not None:
            raise HTTPException(status_code=400, detail="Este link magico ja foi utilizado.")
        if magic_link["expires_at"] <= now_iso:
            raise HTTPException(status_code=400, detail="Link magico expirado.")

        email = magic_link["email"]
        user = connection.execute(
            "SELECT * FROM users WHERE email = ? LIMIT 1",
            (email,),
        ).fetchone()

        if user is None:
            user_id = uuid.uuid4().hex
            created_at = now_iso
            connection.execute(
                """
                INSERT INTO users (id, email, name, role, beta_access, created_at, updated_at)
                VALUES (?, ?, NULL, NULL, 1, ?, ?)
                """,
                (user_id, email, created_at, created_at),
            )
            user = connection.execute(
                "SELECT * FROM users WHERE id = ? LIMIT 1",
                (user_id,),
            ).fetchone()
        elif not bool(user["beta_access"]):
            connection.execute(
                """
                UPDATE users
                SET beta_access = 1, updated_at = ?
                WHERE id = ?
                """,
                (now_iso, user["id"]),
            )
            user = connection.execute(
                "SELECT * FROM users WHERE id = ? LIMIT 1",
                (user["id"],),
            ).fetchone()

        connection.execute(
            "UPDATE magic_links SET used_at = ? WHERE id = ?",
            (now_iso, magic_link["id"]),
        )

        session_id = uuid.uuid4().hex
        session_token = create_session_token(
            user_id=user["id"],
            session_id=session_id,
            expires_at=expires_at,
        )
        connection.execute(
            """
            INSERT INTO sessions (
                id, user_id, token, expires_at, revoked_at, created_at, user_agent, ip
            )
            VALUES (?, ?, ?, ?, NULL, ?, ?, ?)
            """,
            (
                session_id,
                user["id"],
                session_token,
                to_iso(expires_at),
                now_iso,
                request.headers.get("user-agent"),
                request.client.host if request.client else None,
            ),
        )
        connection.commit()

    set_session_cookie(response, session_token)
    return AuthUserResponse(ok=True, user=row_to_auth_user(user))


@auth_router.get("/me", response_model=AuthUserResponse)
def get_me(session: SessionContext = Depends(get_current_session)) -> AuthUserResponse:
    return AuthUserResponse(ok=True, user=session.user)


@auth_router.post("/onboarding", response_model=AuthUserResponse)
def complete_onboarding(
    payload: OnboardingPayload,
    session: SessionContext = Depends(get_current_session),
) -> AuthUserResponse:
    now_iso = to_iso(utc_now())
    name = payload.name.strip()
    if not name:
        raise HTTPException(status_code=400, detail="Nome obrigatorio.")

    with closing(db_connection()) as connection:
        connection.execute(
            """
            UPDATE users
            SET name = ?, role = ?, updated_at = ?
            WHERE id = ?
            """,
            (name, payload.role, now_iso, session.user.id),
        )
        connection.commit()
        user = connection.execute(
            "SELECT * FROM users WHERE id = ? LIMIT 1",
            (session.user.id,),
        ).fetchone()

    return AuthUserResponse(ok=True, user=row_to_auth_user(user))


@auth_router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
def logout(
    response: Response,
    session: SessionContext = Depends(get_current_session),
) -> Response:
    now_iso = to_iso(utc_now())

    with closing(db_connection()) as connection:
        connection.execute(
            """
            UPDATE sessions
            SET revoked_at = ?
            WHERE id = ? AND revoked_at IS NULL
            """,
            (now_iso, session.session_id),
        )
        connection.commit()

    clear_session_cookie(response)
    response.status_code = status.HTTP_204_NO_CONTENT
    return response
