from __future__ import annotations

from datetime import UTC, datetime

from fastapi import HTTPException, status
from sqlalchemy import func, select, true
from sqlalchemy.orm import Session

from app.core.config import get_settings
from app.core.security import create_access_token, hash_password, verify_password
from app.models.admin import Admin
from app.models.chat import ChatMessage, ChatSession
from app.models.knowledge import Knowledge
from app.models.logs import AiRequestLog, OperationLog
from app.models.user import User
from app.services.security_state_service import SecurityStateService
from app.services.admin_permission_service import permissions_for


DEVELOPMENT_ENVS = {"local", "dev", "development", "docker", "test", "testing"}
UNSAFE_BOOTSTRAP_PASSWORDS = {
    "123456",
    "admin",
    "admin123456",
    "change-me-in-production",
    "password",
    "replace-with-strong-password",
}


def _is_production_env(app_env: str) -> bool:
    return app_env.strip().lower() not in DEVELOPMENT_ENVS


def _get_bootstrap_admin_config() -> tuple[str, str, str]:
    settings = get_settings()
    username = settings.bootstrap_admin_username.strip()
    password = settings.bootstrap_admin_password
    name = settings.bootstrap_admin_name.strip() or username

    if not username or not password:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="未配置首个管理员初始化账号，请配置 BOOTSTRAP_ADMIN_USERNAME/BOOTSTRAP_ADMIN_PASSWORD",
        )

    if _is_production_env(settings.app_env):
        normalized_password = password.strip().lower()
        if normalized_password in UNSAFE_BOOTSTRAP_PASSWORDS or len(password) < 12:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="生产环境首个管理员初始化密码不安全，请重新配置 BOOTSTRAP_ADMIN_PASSWORD",
            )

    return username, password, name


class AdminAuthService:
    @classmethod
    def login(cls, db: Session, username: str, password: str, ip: str = "unknown") -> dict:
        failure_key = f"auth:admin:fail:{username.lower()}:{ip}"
        SecurityStateService.enforce_limit(
            f"rate:admin_login:ip:{ip}", limit=30, window_seconds=600, message="登录请求过于频繁，请稍后再试"
        )
        SecurityStateService.ensure_not_locked(
            failure_key, limit=5, message="账号或当前网络连续登录失败次数过多，请十五分钟后再试"
        )
        cls.ensure_bootstrap_admin(db)
        admin = db.scalar(select(Admin).where(Admin.username == username))
        password_valid = False
        if admin is not None:
            password_valid = (
                password == get_settings().bootstrap_admin_password
                if admin.is_super_admin
                else verify_password(password, admin.password)
            )
        if admin is None or not password_valid:
            SecurityStateService.record_failure(
                failure_key,
                limit=5,
                lock_seconds=900,
                message="账号或当前网络连续登录失败次数过多，请十五分钟后再试",
            )
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="管理员账号或密码错误")
        if admin.status != 1:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="管理员已禁用")
        SecurityStateService.clear(failure_key)

        admin.last_login_at = datetime.now(UTC).replace(tzinfo=None)
        db.add(admin)
        db.commit()
        db.refresh(admin)

        token, expired_at = create_access_token(str(admin.id), "admin")
        return {
            "token": token,
            "expiredAt": expired_at,
            "admin": {
                "id": admin.id,
                "username": admin.username,
                "name": admin.name,
                "status": admin.status,
                "isSuperAdmin": bool(admin.is_super_admin),
                "mustChangePassword": bool(admin.must_change_password),
                "permissions": sorted(permissions_for(admin)),
            },
        }

    @staticmethod
    def ensure_bootstrap_admin(db: Session) -> None:
        has_admin = db.scalar(select(func.count(Admin.id))) or 0
        if has_admin:
            return
        username, password, name = _get_bootstrap_admin_config()
        admin = Admin(
            username=username,
            password=hash_password(password),
            name=name,
            status=1,
            must_change_password=0,
            is_super_admin=1,
        )
        db.add(admin)
        db.commit()


class AdminDashboardService:
    @staticmethod
    def stats(db: Session, start: datetime | None = None, end: datetime | None = None) -> dict:
        from sqlalchemy import and_

        # 基础表查询条件（时间范围）
        user_filter = [User.is_deleted == 0]
        session_filter = [ChatSession.is_deleted == 0]
        msg_filter = []
        ai_filter = []

        if start:
            user_filter.append(User.created_at >= start)
            session_filter.append(ChatSession.created_at >= start)
            msg_filter.append(ChatMessage.created_at >= start)
            ai_filter.append(AiRequestLog.created_at >= start)
        if end:
            user_filter.append(User.created_at <= end)
            session_filter.append(ChatSession.created_at <= end)
            msg_filter.append(ChatMessage.created_at <= end)
            ai_filter.append(AiRequestLog.created_at <= end)

        user_where = and_(true(), *user_filter)
        session_where = and_(true(), *session_filter)
        message_where = and_(true(), *msg_filter)
        ai_where = and_(true(), *ai_filter)
        cost_currency = db.scalar(
            select(AiRequestLog.currency)
            .where(ai_where, AiRequestLog.currency.is_not(None))
            .order_by(AiRequestLog.id.desc())
            .limit(1)
        )
        cost_breakdown = [
            {
                "scene": question_type or "unknown",
                "modelName": model_name or "未知模型",
                "requestCount": int(request_count or 0),
                "inputToken": int(input_token or 0),
                "outputToken": int(output_token or 0),
                "totalToken": int(total_token or 0),
                "estimatedCost": float(estimated_cost or 0),
                "currency": currency,
            }
            for (
                question_type,
                model_name,
                currency,
                request_count,
                input_token,
                output_token,
                total_token,
                estimated_cost,
            ) in db.execute(
                select(
                    AiRequestLog.question_type,
                    AiRequestLog.model_name,
                    AiRequestLog.currency,
                    func.count(AiRequestLog.id),
                    func.coalesce(func.sum(AiRequestLog.input_token), 0),
                    func.coalesce(func.sum(AiRequestLog.output_token), 0),
                    func.coalesce(func.sum(AiRequestLog.total_token), 0),
                    func.coalesce(func.sum(AiRequestLog.estimated_cost), 0),
                )
                .where(ai_where)
                .group_by(
                    AiRequestLog.question_type,
                    AiRequestLog.model_name,
                    AiRequestLog.currency,
                )
                .order_by(func.count(AiRequestLog.id).desc())
            ).all()
        ]
        currencies = {item["currency"] for item in cost_breakdown if item["currency"]}
        if len(currencies) > 1:
            cost_currency = "MIXED"
        return {
            "userCount": db.scalar(select(func.count(User.id)).where(user_where)) or 0,
            "sessionCount": db.scalar(select(func.count(ChatSession.id)).where(session_where)) or 0,
            "messageCount": db.scalar(select(func.count(ChatMessage.id)).where(message_where)) or 0,
            "aiRequestCount": db.scalar(select(func.count(AiRequestLog.id)).where(ai_where)) or 0,
            "knowledgeCount": db.scalar(select(func.count(Knowledge.id))) or 0,
            "inputToken": db.scalar(select(func.coalesce(func.sum(AiRequestLog.input_token), 0)).where(ai_where)) or 0,
            "outputToken": db.scalar(select(func.coalesce(func.sum(AiRequestLog.output_token), 0)).where(ai_where)) or 0,
            "totalToken": db.scalar(select(func.coalesce(func.sum(AiRequestLog.total_token), 0)).where(ai_where)) or 0,
            "estimatedCost": float(db.scalar(select(func.coalesce(func.sum(AiRequestLog.estimated_cost), 0)).where(ai_where)) or 0),
            "costCurrency": cost_currency,
            "costBreakdown": cost_breakdown,
        }


class OperationLogService:
    @staticmethod
    def write(
        db: Session,
        *,
        admin_id: int | None,
        module: str,
        action: str,
        target_id: int | None = None,
        result: str = "SUCCESS",
    ) -> None:
        db.add(
            OperationLog(
                admin_id=admin_id,
                module=module,
                action=action,
                target_id=target_id,
                result=result,
            )
        )
