from typing import Any, List, Optional
import logging
import json
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session

from app.api.v1 import deps
from app.core import security
from app.models.user import User, UserRole
from app.models.mapping import AppUserMapping
from app.schemas.token import Token, LoginRequest, RejectRequest
from app.services.hydra_service import hydra_service

router = APIRouter()
logger = logging.getLogger(__name__)

@router.get("/login-request", summary="获取登录请求信息 (OIDC)")
def get_login_request(
    challenge: str,
    current_user: Optional[User] = Depends(deps.get_current_active_user_optional)
):
    """
    从 Hydra 获取登录请求信息。
    前端调用此接口以检查是否应跳过登录（如果响应中 skip=true）。
    如果用户在统一认证平台已有会话，也会自动接受。
    """
    try:
        req = hydra_service.get_login_request(challenge)
        if req.skip:
            logger.info(f"Skipping login for challenge {challenge}, subject: {req.subject}")
            # If Hydra says skip, we just accept it immediately
            return hydra_service.accept_login_request(challenge, subject=req.subject)
            
        # 如果不是 skip，但用户在平台已登录，则自动接受
        if current_user:
            logger.info(f"Auto-accepting login for challenge {challenge}, using platform session, subject: {current_user.id}")
            return hydra_service.accept_login_request(challenge, subject=str(current_user.id))
            
        return req
    except Exception as e:
        logger.exception(f"Failed to get login request for challenge: {challenge}")
        raise HTTPException(status_code=400, detail=str(e))

@router.post("/login/accept", summary="接受登录请求 (OIDC)")
def accept_login(
    challenge: str,
    login_data: LoginRequest,
    db: Session = Depends(deps.get_db)
):
    """
    用户提交凭据 -> 验证 -> 接受登录请求。
    """
    user = db.query(User).filter(User.mobile == login_data.mobile).first()
    if not user or not security.verify_password(login_data.password, user.password_hash):
        # We don't reject the request immediately to allow retry,
        # but in a strict flow we might. Here we just return 401.
        logger.warning(f"Login failed for user {login_data.mobile}: Invalid credentials")
        raise HTTPException(status_code=401, detail="手机号或密码错误")
    
    if user.status != "ACTIVE":
        logger.warning(f"Login failed for user {login_data.mobile}: User not active")
        raise HTTPException(status_code=400, detail="用户状态不正常")

    try:
        logger.info(f"Accepting login request for user {user.mobile} (ID: {user.id}), challenge: {challenge}")
        return hydra_service.accept_login_request(challenge, subject=str(user.id))
    except Exception as e:
        logger.exception(f"Failed to accept login request for challenge: {challenge}")
        raise HTTPException(status_code=500, detail=str(e))

@router.post("/login/reject", summary="拒绝登录请求 (OIDC)")
def reject_login(
    challenge: str,
    reject_data: RejectRequest
):
    """
    拒绝登录请求。
    用于在用户取消登录或发生错误时拒绝 OIDC 登录请求。
    
    标准 OAuth2 错误码：
    - access_denied: 用户拒绝访问
    - invalid_request: 请求无效
    - server_error: 服务器错误
    """
    try:
        logger.info(f"Rejecting login request for challenge: {challenge}, error: {reject_data.error}")
        result = hydra_service.reject_login_request(
            challenge=challenge,
            error=reject_data.error,
            error_description=reject_data.error_description or ""
        )
        return result
    except Exception as e:
        logger.exception(f"Failed to reject login request for challenge: {challenge}")
        raise HTTPException(status_code=400, detail=str(e))

@router.get("/consent-request", summary="获取同意请求信息 (OIDC)")
def get_consent_request(
    challenge: str,
    db: Session = Depends(deps.get_db)
):
    """
    获取同意请求信息。
    通常自动接受内部用户的同意请求，但需要注入 Claims（映射账户）。
    """
    try:
        req = hydra_service.get_consent_request(challenge)
        
        # 添加调试日志
        client_id = req.client.client_id if req.client else None
        user_id = int(req.subject) if req.subject else None
        logger.info(f"Consent request - skip: {req.skip}, subject: {req.subject}, client_id: {client_id}, user_id: {user_id}")
        
        # If skip is true, Hydra remembers consent.
        # But we still might want to refresh claims?
        # 即使 skip，也应该尝试注入 claims
        if req.skip:
             logger.info(f"Skipping consent for challenge {challenge}, subject: {req.subject}")
             
             # 尝试查找 mapping 和 user
             id_token_claims = {}
             if client_id and user_id:
                 mapping = db.query(AppUserMapping).join(AppUserMapping.application).filter(
                     AppUserMapping.user_id == user_id,
                     AppUserMapping.application.has(app_id=client_id)
                 ).first()
                 
                 if mapping:
                     id_token_claims["preferred_username"] = mapping.mapped_key
                     id_token_claims["email"] = mapping.mapped_email
                     logger.info(f"[SKIP] Injecting claims for user {user_id} in client {client_id}: {mapping.mapped_key}")
                 else:
                     logger.warning(f"[SKIP] No mapping found for user {user_id} in client {client_id}")
             
             user = db.query(User).filter(User.id == user_id).first() if user_id else None
             if user:
                 id_token_claims["phone_number"] = user.mobile
                 logger.info(f"[SKIP] Added phone_number: {user.mobile}")
                 
                 # 根据用户角色注入策略
                 policies = []
                 if user.role == UserRole.SUPER_ADMIN:
                     policies = ["consoleAdmin", "diagnostics", "readonly", "readwrite"]
                 elif user.role == UserRole.DEVELOPER:
                     policies = ["diagnostics", "readonly", "readwrite"]
                 # ORDINARY_USER 没有策略，保持空数组
                 
                 if policies:
                     id_token_claims["policies"] = policies
                     logger.info(f"[SKIP] Added policies for role {user.role}: {policies}")
             else:
                 logger.warning(f"[SKIP] User {user_id} not found")
             
             claims_summary = json.dumps(id_token_claims, ensure_ascii=False, indent=2)
             logger.info(f"[SKIP] Final claims:\n{claims_summary}")
             
             return hydra_service.accept_consent_request(
                 challenge, 
                 grant_scope=req.requested_scope,
                 id_token_claims=id_token_claims
             )
        
        # Auto-accept logic:
        # 1. Identify Client (App)
        # 2. Identify User
        # 3. Find Mapping
        client_id = req.client.client_id
        user_id = int(req.subject) # subject is user.id from login
        
        logger.info(f"Processing consent - user_id: {user_id}, client_id: {client_id}")
        
        # Find mapping for this specific app (client_id matches app_id in our DB)
        # Note: In Applications table we used 'app_id' string.
        mapping = db.query(AppUserMapping).join(AppUserMapping.application).filter(
            AppUserMapping.user_id == user_id,
            # Assuming client_id in Hydra == app_id in our DB
            AppUserMapping.application.has(app_id=client_id)
        ).first()

        logger.info(f"Mapping query result: {mapping.mapped_key if mapping else 'None'}")

        id_token_claims = {}
        if mapping:
            # We inject the mapped key (e.g. email, username) into the ID Token
            # You can standardize the claim name, e.g. "preferred_username" or "ext_id"
            id_token_claims["preferred_username"] = mapping.mapped_key
            id_token_claims["email"] = mapping.mapped_email
            logger.info(f"Injecting claims for user {user_id} in client {client_id}: {mapping.mapped_key}")
        else:
            logger.warning(f"No mapping found for user {user_id} in client {client_id}, minimal claims injected.")
        
        # Also inject mobile number if needed
        user = db.query(User).filter(User.id == user_id).first()
        if user:
            id_token_claims["phone_number"] = user.mobile
            logger.info(f"Added phone_number: {user.mobile}")
            
            # 根据用户角色注入策略
            policies = []
            if user.role == UserRole.SUPER_ADMIN:
                policies = ["consoleAdmin", "diagnostics", "readonly", "readwrite"]
            elif user.role == UserRole.DEVELOPER:
                policies = ["diagnostics", "readonly", "readwrite"]
            # ORDINARY_USER 没有策略，保持空数组
            
            if policies:
                id_token_claims["policies"] = policies
                logger.info(f"Added policies for role {user.role}: {policies}")
        else:
            logger.warning(f"User {user_id} not found")
        
        # 记录完整的 id_token claims 信息（用于调试）
        claims_summary = json.dumps(id_token_claims, ensure_ascii=False, indent=2)
        logger.info(
            f"准备注入到 id_token 的完整 claims (user_id: {user_id}, client_id: {client_id}):\n{claims_summary}"
        )

        return hydra_service.accept_consent_request(
            challenge,
            grant_scope=req.requested_scope,
            id_token_claims=id_token_claims
        )

    except Exception as e:
        logger.exception(f"Failed to process consent request for challenge: {challenge}")
        raise HTTPException(status_code=400, detail=str(e))

@router.post("/consent/reject", summary="拒绝同意请求 (OIDC)")
def reject_consent(
    challenge: str,
    reject_data: RejectRequest
):
    """
    拒绝同意请求。
    用于在用户拒绝授权或发生错误时拒绝 OIDC 同意请求。
    
    标准 OAuth2 错误码：
    - access_denied: 用户拒绝访问
    - invalid_request: 请求无效
    - server_error: 服务器错误
    """
    try:
        logger.info(f"Rejecting consent request for challenge: {challenge}, error: {reject_data.error}")
        result = hydra_service.reject_consent_request(
            challenge=challenge,
            error=reject_data.error,
            error_description=reject_data.error_description or ""
        )
        return result
    except Exception as e:
        logger.exception(f"Failed to reject consent request for challenge: {challenge}")
        raise HTTPException(status_code=400, detail=str(e))


@router.get("/logout-request", summary="获取登出请求信息 (OIDC)")
def get_logout_request(challenge: str):
    """
    从 Hydra 获取登出请求信息。
    """
    try:
        return hydra_service.get_logout_request(challenge)
    except Exception as e:
        logger.exception(f"Failed to get logout request for challenge: {challenge}")
        raise HTTPException(status_code=400, detail=str(e))

@router.post("/logout/accept", summary="接受登出请求 (OIDC)")
def accept_logout(
    challenge: str
):
    """
    接受登出请求，返回 redirect_to。
    """
    try:
        logger.info(f"Accepting logout request for challenge: {challenge}")
        return hydra_service.accept_logout_request(challenge)
    except Exception as e:
        logger.exception(f"Failed to accept logout request for challenge: {challenge}")
        raise HTTPException(status_code=500, detail=str(e))