"""Auth dependencies for FastAPI routes.

  get_current_user(token) — extracts and validates the JWT from
  the Authorization header. Raises 401 on missing/bad/expired tokens.

  require_role(*roles) — factory returning a dependency that enforces
  user.role ∈ roles. Use as `Depends(require_role("admin"))` in
  router decorators.

Apply at router-inclusion time:
    app.include_router(demand.router, prefix=..., dependencies=[Depends(get_current_user)])
"""
from __future__ import annotations

import secrets
from typing import Optional

from fastapi import Depends, Header, HTTPException, Request, status

from backend.config import settings
from backend.services.auth_service import decode_token

# Paths the machine ingest key is allowed to reach (suffixes, prefix-agnostic).
# Used by the GLOBAL router gate so the key unlocks ONLY automated ingestion —
# never the rest of the protected API.
_INGEST_PATH_SUFFIXES = (
    "/demand/upload-sales",
    "/demand/upload-stock",
    "/supply/upload-incoming",
)


class CurrentUser(dict):
    """Thin wrapper so route handlers can do `user.user_id` if they want,
    while still being a dict at the wire level."""
    @property
    def user_id(self) -> int:  # noqa: D401
        return int(self.get("user_id", 0))

    @property
    def username(self) -> str:
        return str(self.get("username", ""))

    @property
    def role(self) -> str:
        return str(self.get("role", ""))


def get_current_user(
    authorization: Optional[str] = Header(None),
) -> CurrentUser:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing or malformed Authorization header",
            headers={"WWW-Authenticate": "Bearer"},
        )
    token = authorization.split(None, 1)[1].strip()
    payload = decode_token(token)
    if not payload:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return CurrentUser(payload)


def require_ingest_or_user(
    x_ingest_key: Optional[str] = Header(None),
    authorization: Optional[str] = Header(None),
) -> CurrentUser:
    """Gate for the automated ERP ingestion endpoints (/upload-*).

    Accepts EITHER:
      * a machine key in the `X-Ingest-Key` header matching settings.INGEST_API_KEY
        (used by n8n / the ingest worker) — returns a synthetic 'ingest' user; OR
      * a normal logged-in session (Bearer JWT) — so the in-app Upload pages keep
        working unchanged.

    Returns 401 if neither is valid. When INGEST_API_KEY is unset, only the
    session path works (machine access is effectively disabled)."""
    key = settings.INGEST_API_KEY
    if key and x_ingest_key and secrets.compare_digest(x_ingest_key, key):
        return CurrentUser({"user_id": 0, "username": "ingest", "role": "ingest"})
    # Fall back to a normal authenticated user.
    return get_current_user(authorization)


def _ingest_key_ok(x_ingest_key: Optional[str]) -> bool:
    key = settings.INGEST_API_KEY
    return bool(key and x_ingest_key and secrets.compare_digest(x_ingest_key, key))


def session_or_ingest(
    request: Request,
    x_ingest_key: Optional[str] = Header(None),
    authorization: Optional[str] = Header(None),
) -> CurrentUser:
    """GLOBAL router gate. Normally requires a valid session (Bearer JWT), but
    for the designated ingestion paths a valid X-Ingest-Key is accepted instead
    — so n8n/the worker can POST without a user login. The key is scoped to
    those paths only; on any other route it's ignored and a session is
    required. Use as the router-include dependency."""
    path = request.url.path
    if any(path.endswith(suf) for suf in _INGEST_PATH_SUFFIXES) and _ingest_key_ok(x_ingest_key):
        return CurrentUser({"user_id": 0, "username": "ingest", "role": "ingest"})
    return get_current_user(authorization)


def require_ingest_or_section(section_id: str):
    """Like require_section, but ALSO accepts the machine ingest key. Lets the
    automated worker post while preserving the section-level authorization for
    human (browser-session) callers."""
    from backend.services.permissions import role_can

    def _checker(
        x_ingest_key: Optional[str] = Header(None),
        authorization: Optional[str] = Header(None),
    ) -> CurrentUser:
        if _ingest_key_ok(x_ingest_key):
            return CurrentUser({"user_id": 0, "username": "ingest", "role": "ingest"})
        user = get_current_user(authorization)
        if not role_can(user.role, section_id):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Role '{user.role}' has no access to section '{section_id}'",
            )
        return user

    return _checker


def require_role(*allowed: str):
    """Return a dependency that fails with 403 if user.role not in allowed.
    Comparison is case-insensitive but preserves the configured names
    (Admin / Nabava / Marketing / …)."""
    allowed_set = {r.lower() for r in allowed}

    def _checker(user: CurrentUser = Depends(get_current_user)) -> CurrentUser:
        if user.role.lower() not in allowed_set:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Requires role: {', '.join(sorted(allowed_set))}",
            )
        return user

    return _checker


def require_section(section_id: str):
    """Return a dependency that fails with 403 if the user's role can't see
    `section_id` per the role_permissions.yaml matrix."""
    # Lazy import to avoid circular (permissions loads at import time and
    # doesn't depend on FastAPI).
    from backend.services.permissions import role_can

    def _checker(user: CurrentUser = Depends(get_current_user)) -> CurrentUser:
        if not role_can(user.role, section_id):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Role '{user.role}' has no access to section '{section_id}'",
            )
        return user

    return _checker
