"""
Demandas Auth Service
Autenticação JWT própria para a ferramenta de demandas.
Substitui o Supabase Auth.

Roda em 127.0.0.1:3009
"""
import os
from datetime import datetime, timedelta, timezone
from typing import Optional, Literal
from contextlib import contextmanager

import bcrypt
import psycopg2
import psycopg2.extras
from fastapi import FastAPI, HTTPException, Depends, status, Header
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, EmailStr
from jose import jwt, JWTError


# Config
JWT_SECRET = os.environ.get("DEMANDAS_JWT_SECRET", "demandas_jwt_secret_climb_2026_mia")
JWT_ALGORITHM = "HS256"
JWT_EXP_HOURS = 8
DB_DSN = "dbname=demandas user=mia"

Permissao = Literal["super_admin", "admin", "gestor", "colaborador"]


# Models
class LoginRequest(BaseModel):
    email: str
    password: str


class ChangePasswordRequest(BaseModel):
    current_password: str
    new_password: str


class AdminCreateUserRequest(BaseModel):
    email: str
    password: str
    nome: str
    cargo: Optional[str] = None
    permissao: Permissao = "colaborador"
    workspace_id: Optional[str] = None


class AdminSetPasswordRequest(BaseModel):
    user_id: str
    new_password: str


# App
app = FastAPI(title="Demandas Auth", version="1.0.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
)


# DB
@contextmanager
def db_conn():
    conn = psycopg2.connect(DB_DSN)
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()


@contextmanager
def db_cursor():
    with db_conn() as conn:
        cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
        try:
            yield cur
        finally:
            cur.close()


# Crypto helpers
def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def verify_password(password: str, hashed: str) -> bool:
    if not hashed or hashed == "MIGRATED_NO_PASSWORD":
        return False
    try:
        return bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
    except Exception:
        return False


def make_token(user_id: str, email: str, permissao: str, workspace_id: Optional[str]) -> str:
    now = datetime.now(timezone.utc)
    payload = {
        "sub": user_id,
        "email": email,
        "permissao": permissao,
        "workspace_id": workspace_id,
        "role": "web_anon",
        "iat": int(now.timestamp()),
        "exp": int((now + timedelta(hours=JWT_EXP_HOURS)).timestamp()),
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)


def decode_token(token: str) -> dict:
    try:
        return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
    except JWTError as e:
        raise HTTPException(status_code=401, detail=f"Token inválido: {e}")


# Dependency: extrai e valida token
def get_current_user(authorization: Optional[str] = Header(None)) -> dict:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="Token ausente")
    token = authorization.split(" ", 1)[1].strip()
    payload = decode_token(token)
    return payload


def require_admin(current=Depends(get_current_user)) -> dict:
    if current.get("permissao") not in ("admin", "super_admin"):
        raise HTTPException(status_code=403, detail="Acesso restrito a administradores")
    return current


# Endpoints
@app.get("/")
def root():
    return {"service": "demandas-auth", "status": "ok", "version": "1.0.0"}


@app.get("/health")
def health():
    try:
        with db_cursor() as cur:
            cur.execute("SELECT 1")
            cur.fetchone()
        return {"status": "ok", "db": "ok"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"db error: {e}")


@app.post("/auth/login")
def login(req: LoginRequest):
    with db_cursor() as cur:
        cur.execute(
            """
            SELECT u.id, u.email, u.password,
                   p.nome, p.permissao, p.workspace_id, p.ativo
            FROM auth_users u
            LEFT JOIN profiles p ON p.id = u.id
            WHERE lower(u.email) = lower(%s)
            """,
            (req.email,),
        )
        row = cur.fetchone()

    if not row:
        raise HTTPException(status_code=401, detail="Email ou senha inválidos")

    if not verify_password(req.password, row["password"]):
        raise HTTPException(status_code=401, detail="Email ou senha inválidos")

    if row.get("ativo") is False:
        raise HTTPException(status_code=403, detail="Usuário desativado")

    user_id = str(row["id"])
    permissao = row.get("permissao") or "colaborador"
    workspace_id = str(row["workspace_id"]) if row.get("workspace_id") else None

    token = make_token(user_id, row["email"], permissao, workspace_id)

    return {
        "access_token": token,
        "token_type": "bearer",
        "user": {
            "id": user_id,
            "email": row["email"],
            "nome": row.get("nome"),
            "permissao": permissao,
            "workspace_id": workspace_id,
        },
    }


@app.post("/auth/logout")
def logout(current=Depends(get_current_user)):
    # Stateless - o cliente descarta o token
    return {"status": "ok"}


@app.get("/auth/me")
def me(current=Depends(get_current_user)):
    user_id = current["sub"]
    with db_cursor() as cur:
        cur.execute(
            """
            SELECT id, nome, email, cargo, permissao, avatar_url,
                   workspace_id, ativo, criado_em
            FROM profiles WHERE id = %s
            """,
            (user_id,),
        )
        row = cur.fetchone()

    if not row:
        raise HTTPException(status_code=404, detail="Profile não encontrado")

    return {
        "id": str(row["id"]),
        "nome": row["nome"],
        "email": row["email"],
        "cargo": row["cargo"],
        "permissao": row["permissao"],
        "avatar_url": row["avatar_url"],
        "workspace_id": str(row["workspace_id"]) if row["workspace_id"] else None,
        "ativo": row["ativo"],
        "criado_em": row["criado_em"].isoformat() if row["criado_em"] else None,
    }


@app.post("/auth/change-password")
def change_password(req: ChangePasswordRequest, current=Depends(get_current_user)):
    if len(req.new_password) < 6:
        raise HTTPException(status_code=400, detail="Senha deve ter ao menos 6 caracteres")

    user_id = current["sub"]
    with db_cursor() as cur:
        cur.execute("SELECT password FROM auth_users WHERE id = %s", (user_id,))
        row = cur.fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Usuário não encontrado")
        if not verify_password(req.current_password, row["password"]):
            raise HTTPException(status_code=401, detail="Senha atual incorreta")

        new_hash = hash_password(req.new_password)
        cur.execute("UPDATE auth_users SET password = %s WHERE id = %s", (new_hash, user_id))

    return {"status": "ok"}


@app.post("/auth/admin/create-user")
def admin_create_user(req: AdminCreateUserRequest, current=Depends(require_admin)):
    if len(req.password) < 6:
        raise HTTPException(status_code=400, detail="Senha deve ter ao menos 6 caracteres")

    # Admin só pode criar admin/gestor/colaborador. Super_admin só vem por super_admin.
    if req.permissao == "super_admin" and current.get("permissao") != "super_admin":
        raise HTTPException(status_code=403, detail="Apenas super_admin pode criar super_admin")

    # Workspace: se não informado, usa o do criador
    workspace_id = req.workspace_id or current.get("workspace_id")

    # Super_admin pode criar em qualquer workspace; admin só no próprio
    if current.get("permissao") == "admin" and workspace_id != current.get("workspace_id"):
        raise HTTPException(status_code=403, detail="Admin só pode criar usuários no próprio workspace")

    pwd_hash = hash_password(req.password)

    with db_cursor() as cur:
        # Verifica duplicidade
        cur.execute("SELECT id FROM auth_users WHERE lower(email) = lower(%s)", (req.email,))
        if cur.fetchone():
            raise HTTPException(status_code=409, detail="Email já cadastrado")

        # Cria auth_user
        cur.execute(
            "INSERT INTO auth_users (email, password) VALUES (%s, %s) RETURNING id",
            (req.email, pwd_hash),
        )
        new_id = cur.fetchone()["id"]

        # Cria profile
        cur.execute(
            """
            INSERT INTO profiles (id, nome, email, cargo, permissao, workspace_id, ativo)
            VALUES (%s, %s, %s, %s, %s, %s, true)
            ON CONFLICT (id) DO UPDATE SET
                nome = EXCLUDED.nome,
                email = EXCLUDED.email,
                cargo = EXCLUDED.cargo,
                permissao = EXCLUDED.permissao,
                workspace_id = EXCLUDED.workspace_id
            """,
            (new_id, req.nome, req.email, req.cargo, req.permissao, workspace_id),
        )

    return {
        "id": str(new_id),
        "email": req.email,
        "nome": req.nome,
        "permissao": req.permissao,
        "workspace_id": str(workspace_id) if workspace_id else None,
    }


@app.post("/auth/admin/set-password")
def admin_set_password(req: AdminSetPasswordRequest, current=Depends(require_admin)):
    if len(req.new_password) < 6:
        raise HTTPException(status_code=400, detail="Senha deve ter ao menos 6 caracteres")

    with db_cursor() as cur:
        # Verifica se admin tem direito (mesmo workspace se for admin comum)
        if current.get("permissao") == "admin":
            cur.execute("SELECT workspace_id FROM profiles WHERE id = %s", (req.user_id,))
            row = cur.fetchone()
            if not row:
                raise HTTPException(status_code=404, detail="Usuário não encontrado")
            target_ws = str(row["workspace_id"]) if row["workspace_id"] else None
            if target_ws != current.get("workspace_id"):
                raise HTTPException(status_code=403, detail="Admin só pode redefinir senha do próprio workspace")

        pwd_hash = hash_password(req.new_password)
        cur.execute("UPDATE auth_users SET password = %s WHERE id = %s", (pwd_hash, req.user_id))
        if cur.rowcount == 0:
            raise HTTPException(status_code=404, detail="Usuário não encontrado")

    return {"status": "ok"}


@app.get("/auth/admin/users")
def admin_list_users(current=Depends(require_admin)):
    is_super = current.get("permissao") == "super_admin"
    with db_cursor() as cur:
        if is_super:
            cur.execute(
                """
                SELECT id, nome, email, cargo, permissao, avatar_url,
                       workspace_id, ativo, criado_em
                FROM profiles
                ORDER BY criado_em DESC
                """
            )
        else:
            cur.execute(
                """
                SELECT id, nome, email, cargo, permissao, avatar_url,
                       workspace_id, ativo, criado_em
                FROM profiles
                WHERE workspace_id = %s
                ORDER BY criado_em DESC
                """,
                (current.get("workspace_id"),),
            )
        rows = cur.fetchall()

    return [
        {
            "id": str(r["id"]),
            "nome": r["nome"],
            "email": r["email"],
            "cargo": r["cargo"],
            "permissao": r["permissao"],
            "avatar_url": r["avatar_url"],
            "workspace_id": str(r["workspace_id"]) if r["workspace_id"] else None,
            "ativo": r["ativo"],
            "criado_em": r["criado_em"].isoformat() if r["criado_em"] else None,
        }
        for r in rows
    ]
