"""租户登录 — POST /v1/auth/login · preflight · 密码 RSA 密文。"""

from __future__ import annotations

import secrets
from typing import TYPE_CHECKING, Any

from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field

from niannian.http.deps import _resolve_bearer_token
from niannian.tenant.capabilities import capabilities_for_login
from niannian.tenant.context import resolve_request_context, tenant_login_enabled
from niannian.tenant.intranet import is_desktop_agent_request, owner_console_login_allowed
from niannian.tenant.login_crypto import (
    login_key_id,
    public_key_pem,
    resolve_owner_password,
)
from niannian.tenant.registry import TenantUserRegistry, validate_username
from niannian.tenant.sessions import TenantSessionStore

if TYPE_CHECKING:
    from niannian.client import NianNian


class LoginBody(BaseModel):
    username: str = Field(..., min_length=1)
    password: str | None = None
    password_encrypted: str | None = None
    key_id: str | None = None


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


def _verify_owner_password(config: dict[str, Any], password: str | None) -> bool:
    expected = _resolve_bearer_token(config)
    if not expected or not password:
        return False
    return secrets.compare_digest(str(password).strip(), expected)


def _login_role(row: dict[str, Any]) -> str:
    return str(row.get("role", "tenant")).lower()


def _allow_plaintext_owner_password(config: dict[str, Any], *, from_agent: bool) -> bool:
    tenant_auth = config.get("tenant_auth") or {}
    if not isinstance(tenant_auth, dict):
        return not from_agent
    if from_agent:
        return bool(tenant_auth.get("allow_plaintext_owner_password_agent", False))
    return bool(tenant_auth.get("allow_plaintext_owner_password_console", True))


def _auth_payload(
    *,
    username: str,
    role: str,
    token: str,
    expires_at: str,
    desktop_agent: bool,
    intranet_console: bool,
) -> dict[str, Any]:
    return {
        "username": username,
        "role": role,
        "capabilities": capabilities_for_login(
            role,
            desktop_agent=desktop_agent,
            intranet_console=intranet_console,
        ),
        "token": token,
        "expires_at": expires_at,
    }


def create_auth_router(nn: NianNian) -> APIRouter:
    router = APIRouter(prefix="/v1/auth", tags=["auth"])
    registry = TenantUserRegistry(nn._home)
    sessions = TenantSessionStore(nn._home)

    @router.get("/login-key")
    def login_key() -> dict[str, Any]:
        return {
            "key_id": login_key_id(nn._home),
            "public_key_pem": public_key_pem(nn._home),
            "algorithm": "RSA-OAEP-SHA256",
        }

    @router.post("/preflight")
    def preflight(body: PreflightBody) -> dict[str, Any]:
        if not tenant_login_enabled(nn._config, home=nn._home):
            raise HTTPException(
                status_code=403,
                detail={"code": "tenant_login_disabled", "message": "tenant login is disabled"},
            )
        try:
            username = validate_username(body.username)
        except ValueError as exc:
            raise HTTPException(
                status_code=404,
                detail={"code": "user_not_found", "message": "user not found"},
            ) from exc
        row = registry.get_user(username)
        if row is None or str(row.get("status", "")).lower() != "active":
            raise HTTPException(
                status_code=404,
                detail={"code": "user_not_found", "message": "user not found"},
            )
        role = _login_role(row)
        if role == "owner":
            return {
                "username": username,
                "role": role,
                "requires_password": True,
                "key_id": login_key_id(nn._home),
                "public_key_pem": public_key_pem(nn._home),
                "password_transport": "rsa_oaep_required",
            }
        if not registry.is_login_eligible(username):
            raise HTTPException(
                status_code=404,
                detail={"code": "user_not_found", "message": "user not found"},
            )
        return {
            "username": username,
            "role": role,
            "requires_password": False,
        }

    @router.post("/login")
    def login(body: LoginBody, request: Request) -> dict[str, Any]:
        if not tenant_login_enabled(nn._config, home=nn._home):
            raise HTTPException(
                status_code=403,
                detail={"code": "tenant_login_disabled", "message": "tenant login is disabled"},
            )
        try:
            username = validate_username(body.username)
        except ValueError as exc:
            raise HTTPException(
                status_code=404,
                detail={"code": "user_not_found", "message": "user not found"},
            ) from exc
        row = registry.get_user(username)
        if row is None or str(row.get("status", "")).lower() != "active":
            raise HTTPException(
                status_code=404,
                detail={"code": "user_not_found", "message": "user not found"},
            )
        role = _login_role(row)
        agent_hdr = request.headers.get("x-niannian-agent") or request.headers.get("X-NianNian-Agent")
        from_agent = is_desktop_agent_request(agent_header=agent_hdr)
        host = request.headers.get("host")
        client_host = request.client.host if request.client else None

        if role == "owner":
            ok, code = owner_console_login_allowed(
                host=host,
                client_host=client_host,
                config=nn._config,
                from_desktop_agent=from_agent,
            )
            if not ok:
                raise HTTPException(
                    status_code=403,
                    detail={
                        "code": code or "owner_login_intranet_only",
                        "message": "pinn browser console requires NAS intranet URL; use desktop client when away",
                    },
                )
            allow_plain = _allow_plaintext_owner_password(nn._config, from_agent=from_agent)
            if from_agent and not allow_plain and not body.password_encrypted:
                raise HTTPException(
                    status_code=400,
                    detail={
                        "code": "password_encryption_required",
                        "message": "owner login from client requires password_encrypted",
                    },
                )
            try:
                plain = resolve_owner_password(
                    nn._home,
                    password=body.password,
                    password_encrypted=body.password_encrypted,
                    allow_plaintext=allow_plain,
                )
            except ValueError as exc:
                code = str(exc)
                if code == "password_plaintext_forbidden":
                    raise HTTPException(
                        status_code=400,
                        detail={"code": code, "message": "use password_encrypted on public client login"},
                    ) from exc
                raise HTTPException(
                    status_code=400,
                    detail={"code": "password_decrypt_failed", "message": "invalid encrypted password"},
                ) from exc
            if not _verify_owner_password(nn._config, plain):
                raise HTTPException(
                    status_code=401,
                    detail={"code": "invalid_credentials", "message": "invalid username or password"},
                )
            intranet_console = not from_agent and ok
        else:
            if body.password or body.password_encrypted:
                raise HTTPException(
                    status_code=401,
                    detail={"code": "invalid_credentials", "message": "invalid username or password"},
                )
            if not registry.is_login_eligible(username):
                raise HTTPException(
                    status_code=404,
                    detail={"code": "user_not_found", "message": "user not found"},
                )
            intranet_console = False

        registry.touch_last_login(username)
        session = sessions.issue(username, intranet_console=bool(role == "owner" and intranet_console))
        return _auth_payload(
            username=session.username,
            role=role,
            token=session.token,
            expires_at=session.expires_at,
            desktop_agent=from_agent,
            intranet_console=session.intranet_console,
        )

    @router.get("/me")
    def me(request: Request) -> dict[str, Any]:
        ctx = resolve_request_context(request, nn._config, home=nn._home, sessions=sessions)
        if not ctx.session_token:
            raise HTTPException(
                status_code=401,
                detail={"code": "session_required", "message": "valid X-NianNian-Session required"},
            )
        session = sessions.validate(ctx.session_token)
        if session is None:
            raise HTTPException(
                status_code=401,
                detail={"code": "session_invalid", "message": "session expired or invalid"},
            )
        from_agent = is_desktop_agent_request(
            agent_header=request.headers.get("x-niannian-agent") or request.headers.get("X-NianNian-Agent"),
        )
        return _auth_payload(
            username=session.username,
            role=ctx.role,
            token=session.token,
            expires_at=session.expires_at,
            desktop_agent=from_agent,
            intranet_console=session.intranet_console,
        )

    return router
