"""
MFA (Multi-Factor Authentication) API 端点
包含 OTP 和 PassKey 相关功能
"""

import json
from typing import Any, Annotated, Optional

from fastapi import Depends, HTTPException, Body, Request, Response

from app.schemas.mcp import BaseModel as _SchemaBaseModel
from app.schemas.mcp import JsonData as _SchemaJsonData
from app.schemas.mfa import MfaStatusData as _SchemaMfaStatusData
from app.schemas.mfa import OtpGenerateData as _SchemaOtpGenerateData
from app.schemas.mfa import PasskeyInfo as _SchemaPasskeyInfo
from app.schemas.mfa import PasskeyStartData as _SchemaPasskeyStartData
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import Token as _SchemaToken
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
from app.application.security.token import verify_password
from app.application.security.auth import get_configured_auth_service
from app.application.security.user import UserService
from app.application.security.user import (
    get_configured_user_id_lookup,
    get_configured_user_name_lookup,
)
from app.application.security.passkeys import (
    PasskeyService,
)
from app.api.principal import ApiPrincipal
from app.api.dependencies.auth import (
    get_current_active_user,
    get_current_active_user_async,
    get_user_service,
    get_passkey_service,
)
from app.application.security.passkey import (
    PassKeyHelper,
    PassKeyRegistrationOriginMismatchError,
    PassKeyRegistrationVerificationError,
    PasskeyChallengeStore,
)
from app.runtime.log import logger
from app.application.security.otp import OtpUtils

router = ResponseAPIRouter()

# ==================== 辅助函数 ====================


def _build_credential_list(passkeys: list[Any]) -> list[dict[str, Any]]:
    """
    构建凭证列表

    :param passkeys: PassKey 列表
    :return: 凭证字典列表
    """
    return (
        [
            {"credential_id": pk.credential_id, "transports": pk.transports}
            for pk in passkeys
        ]
        if passkeys
        else []
    )


def _extract_and_standardize_credential_id(credential: dict) -> str:
    """
    从凭证中提取并标准化 credential_id

    :param credential: 凭证字典
    :return: 标准化后的 credential_id
    :raises ValueError: 如果凭证无效
    """
    credential_id_raw = credential.get("id") or credential.get("rawId")
    if not credential_id_raw:
        raise ValueError("无效的凭证")
    return PassKeyHelper.standardize_credential_id(credential_id_raw)


def _verify_passkey_and_update(
    credential: dict,
    challenge: str,
    passkey: Any,
    service: PasskeyService,
) -> tuple[bool, int]:
    """
    验证 PassKey 并更新使用时间和签名计数

    :param credential: 凭证字典
    :param challenge: 挑战值
    :param passkey: PassKey 对象
    :return: (验证是否成功, 新的签名计数)
    """
    success, new_sign_count = PassKeyHelper.verify_authentication_response(
        credential=credential,
        expected_challenge=challenge,
        credential_public_key=passkey.public_key,
        credential_current_sign_count=passkey.sign_count,
    )

    if success:
        service.update_last_used(passkey, new_sign_count)

    return success, new_sign_count


# ==================== 请求模型 ====================


class OtpVerifyRequest(_SchemaBaseModel):
    """OTP验证请求"""

    uri: str
    otpPassword: str


class OtpDisableRequest(_SchemaBaseModel):
    """OTP禁用请求"""

    password: str


class PassKeyDeleteRequest(_SchemaBaseModel):
    """PassKey删除请求"""

    passkey_id: int
    password: str


# ==================== 通用 MFA 接口 ====================


@router.get(
    "/status/{username}",
    summary="判断用户是否开启二次验证",
    response_model=_SchemaResponse[_SchemaMfaStatusData],
)
async def mfa_status(
    username: str,
    service: UserService = Depends(get_user_service),
) -> Any:
    """
    检查指定用户是否启用了二次验证
    """
    user = await service.get_by_name(username)
    if not user:
        return _SchemaResponse(success=False, message="用户不存在")

    # 检查是否启用了OTP
    has_otp = user.is_otp

    return _SchemaResponse(success=True, data={"enabled": bool(has_otp)})


# ==================== OTP 相关接口 ====================


@router.post(
    "/otp/generate",
    summary="生成 OTP 验证 URI",
    response_model=_SchemaResponse[_SchemaOtpGenerateData],
)
def otp_generate(
    current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
) -> Any:
    """生成 OTP 密钥及对应的 URI"""
    secret, uri = OtpUtils.generate_secret_key(current_user.name)
    return _SchemaResponse(success=secret != "", data={"secret": secret, "uri": uri})


@router.post("/otp/verify", summary="绑定并验证 OTP", response_model=_SchemaResponse[None])
async def otp_verify(
    data: OtpVerifyRequest,
    service: UserService = Depends(get_user_service),
    current_user: ApiPrincipal = Depends(get_current_active_user_async),
) -> Any:
    """验证用户输入的 OTP 码，验证通过后正式开启 OTP 验证"""
    if not OtpUtils.is_legal(data.uri, data.otpPassword):
        return _SchemaResponse(success=False, message="验证码错误")
    await service.update_otp(
        current_user.name,
        True,
        OtpUtils.get_secret(data.uri),
    )
    return _SchemaResponse(success=True)


@router.post(
    "/otp/disable",
    summary="关闭当前用户的 OTP 验证",
    response_model=_SchemaResponse[None],
)
async def otp_disable(
    data: OtpDisableRequest,
    service: UserService = Depends(get_user_service),
    current_user: ApiPrincipal = Depends(get_current_active_user_async),
) -> Any:
    """关闭当前用户的 OTP 验证功能"""
    # 验证密码
    if not verify_password(data.password, str(current_user.hashed_password)):
        return _SchemaResponse(success=False, message="密码错误")
    await service.update_otp(current_user.name, False, "")
    return _SchemaResponse(success=True)


# ==================== PassKey 相关接口 ====================


class PassKeyRegistrationStart(_SchemaBaseModel):
    """PassKey注册开始请求"""

    name: str = "通行密钥"


class PassKeyRegistrationFinish(_SchemaBaseModel):
    """PassKey注册完成请求"""

    credential: dict[str, _SchemaJsonData]
    transaction_token: str
    name: str = "通行密钥"


class PassKeyAuthenticationStart(_SchemaBaseModel):
    """PassKey认证开始请求"""

    username: Optional[str] = None


class PassKeyAuthenticationFinish(_SchemaBaseModel):
    """PassKey认证完成请求"""

    credential: dict[str, _SchemaJsonData]
    transaction_token: str


@router.post(
    "/passkey/register/start",
    summary="开始注册 PassKey",
    response_model=_SchemaResponse[_SchemaPasskeyStartData],
)
def passkey_register_start(
    current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """开始注册 PassKey - 生成注册选项"""
    try:
        # 获取用户已有的PassKey
        existing_passkeys = service.list_by_user_id(current_user.id)
        existing_credentials = (
            _build_credential_list(existing_passkeys) if existing_passkeys else None
        )

        # 生成注册选项
        options_json, challenge = PassKeyHelper.generate_registration_options(
            user_id=current_user.id,
            username=current_user.name,
            display_name=current_user.settings.get("nickname")
            if current_user.settings
            else None,
            existing_credentials=existing_credentials,
        )

        transaction_token = PasskeyChallengeStore.issue(
            challenge=challenge,
            purpose="registration",
            user_id=current_user.id,
        )
        return _SchemaResponse(
            success=True,
            data={
                "options": json.loads(options_json),
                "transaction_token": transaction_token,
            },
        )
    except Exception as e:
        logger.error(f"生成PassKey注册选项失败: {e}")
        return _SchemaResponse(success=False, message=f"生成注册选项失败: {str(e)}")


@router.post(
    "/passkey/register/finish",
    summary="完成注册 PassKey",
    response_model=_SchemaResponse[None],
)
def passkey_register_finish(
    passkey_req: PassKeyRegistrationFinish,
    current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """完成注册 PassKey - 验证并保存凭证"""
    try:
        challenge_state = PasskeyChallengeStore.consume(
            transaction_token=passkey_req.transaction_token,
            purpose="registration",
        )
        if not challenge_state or challenge_state.user_id != current_user.id:
            return _SchemaResponse(
                success=False,
                message="注册请求已失效，请重新发起注册",
            )

        # 验证注册响应
        credential_id, public_key, sign_count, aaguid = (
            PassKeyHelper.verify_registration_response(
                credential=passkey_req.credential,
                expected_challenge=challenge_state.challenge,
            )
        )

        # 提取transports
        transports = None
        if (
            "response" in passkey_req.credential
            and "transports" in passkey_req.credential["response"]
        ):
            transports = ",".join(passkey_req.credential["response"]["transports"])

        # 保存到数据库
        service.create({
            "user_id": current_user.id,
            "credential_id": credential_id,
            "public_key": public_key,
            "sign_count": sign_count,
            "name": passkey_req.name or "通行密钥",
            "aaguid": aaguid,
            "transports": transports,
        })

        logger.info(f"用户 {current_user.name} 成功注册PassKey: {passkey_req.name}")

        return _SchemaResponse(success=True, message="通行密钥注册成功")
    except PassKeyRegistrationOriginMismatchError:
        return _SchemaResponse(
            success=False,
            message="访问域名与系统配置不一致，请使用配置的域名重试",
        )
    except PassKeyRegistrationVerificationError:
        return _SchemaResponse(
            success=False,
            message="通行密钥注册验证失败，请重新发起注册后重试",
        )
    except Exception as e:
        logger.error(f"注册PassKey失败: {e}")
        return _SchemaResponse(success=False, message="通行密钥注册失败，请稍后重试")


@router.post(
    "/passkey/authenticate/start",
    summary="开始 PassKey 认证",
    response_model=_SchemaResponse[_SchemaPasskeyStartData],
)
def passkey_authenticate_start(
    passkey_req: PassKeyAuthenticationStart = Body(...),
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """开始 PassKey 认证 - 生成认证选项"""
    try:
        existing_credentials = None
        user_id = None

        # 如果指定了用户名，只允许该用户的PassKey
        if passkey_req.username:
            user = get_configured_user_name_lookup()(passkey_req.username)
            existing_passkeys = (
                service.list_by_user_id(user.id) if user else None
            )

            if not user or not existing_passkeys:
                return _SchemaResponse(success=False, message="认证失败")

            existing_credentials = _build_credential_list(existing_passkeys)
            user_id = user.id

        # 生成认证选项
        options_json, challenge = PassKeyHelper.generate_authentication_options(
            existing_credentials=existing_credentials
        )

        transaction_token = PasskeyChallengeStore.issue(
            challenge=challenge,
            purpose="authentication",
            user_id=user_id,
        )
        return _SchemaResponse(
            success=True,
            data={
                "options": json.loads(options_json),
                "transaction_token": transaction_token,
            },
        )
    except Exception as e:
        logger.error(f"生成PassKey认证选项失败: {e}")
        return _SchemaResponse(success=False, message="认证失败")


@router.post(
    "/passkey/authenticate/finish",
    summary="完成 PassKey 认证",
    response_model=_SchemaToken,
    openapi_extra={RAW_RESPONSE_OPENAPI_KEY: True},
)
def passkey_authenticate_finish(
    request: Request,
    response: Response,
    passkey_req: PassKeyAuthenticationFinish,
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """完成 PassKey 认证 - 验证凭证并返回 token"""
    try:
        challenge_state = PasskeyChallengeStore.consume(
            transaction_token=passkey_req.transaction_token,
            purpose="authentication",
        )
        if not challenge_state:
            raise HTTPException(status_code=401, detail="认证请求已失效")

        # 提取并标准化凭证ID
        try:
            credential_id = _extract_and_standardize_credential_id(
                passkey_req.credential
            )
        except ValueError as e:
            logger.warning(f"PassKey认证失败，提供的凭证无效: {e}")
            raise HTTPException(status_code=401, detail="认证失败")

        # 查找PassKey并获取用户
        passkey = service.get_by_credential_id(credential_id)
        user = get_configured_user_id_lookup()(passkey.user_id) if passkey else None
        if not passkey or not user or not user.is_active:
            raise HTTPException(status_code=401, detail="认证失败")
        if challenge_state.user_id is not None and challenge_state.user_id != user.id:
            raise HTTPException(status_code=401, detail="认证失败")

        # 验证认证响应并更新
        success, _ = _verify_passkey_and_update(
            credential=passkey_req.credential,
            challenge=challenge_state.challenge,
            passkey=passkey,
            service=service,
        )

        if not success:
            raise HTTPException(status_code=401, detail="认证失败")

        logger.info(f"用户 {user.name} 通过PassKey认证成功")

        # 生成token
        token = get_configured_auth_service().build_token_response(user)
        set_or_refresh_resource_token_cookie(
            request,
            response,
            _SchemaTokenPayload(
                sub=user.id,
                username=user.name,
                super_user=user.is_superuser,
                level=token.level,
                purpose="authentication",
            ),
        )
        return token
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"PassKey认证失败: {e}")
        raise HTTPException(status_code=401, detail="认证失败")


@router.get(
    "/passkey/list",
    summary="获取当前用户的 PassKey 列表",
    response_model=_SchemaResponse[list[_SchemaPasskeyInfo]],
)
def passkey_list(
    current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """获取当前用户的所有 PassKey"""
    try:
        passkeys = service.list_by_user_id(current_user.id)

        key_list = (
            [
                {
                    "id": pk.id,
                    "name": pk.name,
                    "created_at": pk.created_at.isoformat() if pk.created_at else None,
                    "last_used_at": pk.last_used_at.isoformat()
                    if pk.last_used_at
                    else None,
                    "aaguid": pk.aaguid,
                    "transports": pk.transports,
                }
                for pk in passkeys
            ]
            if passkeys
            else []
        )

        return _SchemaResponse(success=True, data=key_list)
    except Exception as e:
        logger.error(f"获取PassKey列表失败: {e}")
        return _SchemaResponse(success=False, message=f"获取列表失败: {str(e)}")


@router.post(
    "/passkey/delete",
    summary="删除 PassKey",
    response_model=_SchemaResponse[None],
)
async def passkey_delete(
    data: PassKeyDeleteRequest,
    current_user: ApiPrincipal = Depends(get_current_active_user_async),
    service: PasskeyService = Depends(get_passkey_service),
) -> Any:
    """删除指定的 PassKey"""
    try:
        # 验证密码
        if not verify_password(
            data.password, str(current_user.hashed_password)
        ):
            return _SchemaResponse(success=False, message="密码错误")

        success = service.delete_by_id(data.passkey_id, current_user.id)

        if success:
            logger.info(f"用户 {current_user.name} 删除了PassKey: {data.passkey_id}")
            return _SchemaResponse(success=True, message="通行密钥已删除")
        else:
            return _SchemaResponse(success=False, message="通行密钥不存在或无权删除")
    except Exception as e:
        logger.error(f"删除PassKey失败: {e}")
        return _SchemaResponse(success=False, message=f"删除失败: {str(e)}")
