"""/api/auth/* — login, current user, change password.

Login is rate-limited in-process: 5 failed attempts in 60s per IP triggers
a 60s cooldown. Sufficient for an internal app; behind nginx, X-Forwarded-For
is honoured so the real client IP is used.
"""
from __future__ import annotations

import time
from collections import defaultdict, deque
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field
from sqlalchemy import text
from sqlalchemy.orm import Session

from backend.api.middleware.auth import CurrentUser, get_current_user
from backend.models.database import get_db
from backend.services import auth_service

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


# --- Login rate limit ----------------------------------------------------
# 5 failed attempts in 60s per IP → 60s lockout.
_LOGIN_FAILS: dict[str, deque] = defaultdict(deque)
_LOGIN_LOCK: dict[str, float] = {}
_MAX_FAILS = 5
_WINDOW_SEC = 60
_LOCKOUT_SEC = 60


def _client_ip(req: Request) -> str:
    # Honour X-Forwarded-For when behind nginx/CF
    xff = req.headers.get("x-forwarded-for")
    if xff:
        return xff.split(",")[0].strip()
    return req.client.host if req.client else "?"


def _check_login_rate(ip: str) -> None:
    now = time.time()
    if (until := _LOGIN_LOCK.get(ip)) and until > now:
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail=f"Too many failed logins; try again in "
                    f"{int(until - now)}s",
        )
    # purge old fails
    q = _LOGIN_FAILS[ip]
    while q and q[0] < now - _WINDOW_SEC:
        q.popleft()
    if len(q) >= _MAX_FAILS:
        _LOGIN_LOCK[ip] = now + _LOCKOUT_SEC
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail=f"Too many failed logins; locked for {_LOCKOUT_SEC}s",
        )


def _record_login_fail(ip: str) -> None:
    _LOGIN_FAILS[ip].append(time.time())


def _record_login_ok(ip: str) -> None:
    _LOGIN_FAILS.pop(ip, None)
    _LOGIN_LOCK.pop(ip, None)


class LoginRequest(BaseModel):
    username: str = Field(..., min_length=1)
    password: str


class UserOut(BaseModel):
    id: int
    username: str
    display_name: Optional[str] = None
    role: str
    type: Optional[str] = None
    channel: Optional[str] = None
    must_change_password: bool = False


class LoginResponse(BaseModel):
    token: str
    user: UserOut


class ChangePasswordRequest(BaseModel):
    old: str = ""
    new: str = Field(..., min_length=6)


@router.post("/login", response_model=LoginResponse)
def login(body: LoginRequest, request: Request,
          db: Session = Depends(get_db)) -> LoginResponse:
    ip = _client_ip(request)
    _check_login_rate(ip)
    result = auth_service.authenticate(db, body.username, body.password)
    if not result:
        _record_login_fail(ip)
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username or password",
        )
    _record_login_ok(ip)
    return LoginResponse(**result)


@router.get("/me", response_model=UserOut)
def me(user: CurrentUser = Depends(get_current_user),
       db: Session = Depends(get_db)) -> UserOut:
    row = db.execute(
        text("""
            SELECT id, username, display_name, role, type, channel,
                   password_hash IS NULL OR password_hash = '' AS needs_pw
            FROM users WHERE id = :uid
        """),
        {"uid": user.user_id},
    ).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="User not found")
    return UserOut(
        id=row[0], username=row[1], display_name=row[2],
        role=row[3] or "", type=row[4], channel=row[5],
        must_change_password=bool(row[6]),
    )


@router.post("/change-password")
def change_password(body: ChangePasswordRequest,
                    user: CurrentUser = Depends(get_current_user),
                    db: Session = Depends(get_db)) -> dict:
    ok = auth_service.change_password(db, user.user_id, body.old, body.new)
    if not ok:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Old password incorrect or user missing",
        )
    return {"ok": True}


class SectionsResponse(BaseModel):
    role: str
    sections: list[str]                       # flat list, e.g. ["sales_weekly", ...]
    groups: dict[str, list[str]]              # group_key → [section_id, ...]


@router.get("/me/sections", response_model=SectionsResponse)
def my_sections(user: CurrentUser = Depends(get_current_user)) -> SectionsResponse:
    """Sections the current user's role is allowed to see, grouped by
    operational/analytical group. Used by the frontend sidebar to filter
    items and by route guards to 403 unauthorized navigation."""
    from backend.services.permissions import sections_for, groups_for
    return SectionsResponse(
        role=user.role,
        sections=sections_for(user.role),
        groups=groups_for(user.role),
    )
