#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：recorder.py
模块描述：用例录制器 API 路由

作者：czj
创建日期：2026-07-09
最后修改：2026-07-09
"""

import logging
from typing import Optional, List
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.services.case_service import CaseService
from app.schemas.test_case import StepDefinition
from app.utils.id_generator import generate_id

logger = logging.getLogger(__name__)

router = APIRouter()

# ==================== 录制器状态管理 ====================

# 录制器会话存储（单例模式，简单实现）
_recorder_sessions: dict = {}


class RecorderSession:
    """录制器会话"""

    def __init__(self, url: str, module_id: str, headless: bool = False):
        self.session_id = generate_id("rec")
        self.url = url
        self.module_id = module_id
        self.headless = headless
        self.steps: list = []
        self.is_recording = True
        self.created_at = datetime.now()


# ==================== 请求/响应模型 ====================


class StartRecordingRequest(BaseModel):
    """启动录制请求"""
    url: str
    module_id: str
    headless: bool = False


class StartRecordingResponse(BaseModel):
    """启动录制响应"""
    session_id: str
    message: str


class AddStepRequest(BaseModel):
    """添加步骤请求"""
    session_id: str
    step: StepDefinition


class AddAssertionRequest(BaseModel):
    """添加断言请求"""
    session_id: str
    selector: str
    type: str
    expected: Optional[str] = None


class SaveCaseRequest(BaseModel):
    """保存用例请求"""
    session_id: str
    name: str
    module_id: str
    description: Optional[str] = None
    priority: Optional[str] = "medium"
    tags: Optional[List[str]] = None


# ==================== API 端点 ====================


@router.post("/start", response_model=StartRecordingResponse)
async def start_recording(
    request: StartRecordingRequest,
):
    """
    启动录制会话

    创建录制会话，返回 session_id 用于后续步骤提交。

    Args:
        request (StartRecordingRequest): 启动录制请求

    Returns:
        StartRecordingResponse: 录制会话信息
    """
    try:
        session = RecorderSession(
            url=request.url,
            module_id=request.module_id,
            headless=request.headless,
        )
        _recorder_sessions[session.session_id] = session

        logger.info(f"录制会话已启动: session_id={session.session_id}, url={request.url}")
        return StartRecordingResponse(
            session_id=session.session_id,
            message=f"录制会话已启动，session_id={session.session_id}",
        )

    except Exception as e:
        logger.error(f"启动录制会话失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"启动录制失败: {str(e)}")


@router.get("/steps", response_model=dict)
async def get_recorded_steps(
    session_id: str,
):
    """
    获取已录制步骤

    Args:
        session_id (str): 录制会话ID

    Returns:
        dict: 步骤列表
    """
    session = _recorder_sessions.get(session_id)
    if not session:
        raise HTTPException(status_code=404, detail=f"录制会话不存在: {session_id}")

    return {
        "session_id": session_id,
        "steps": session.steps,
        "total": len(session.steps),
    }


@router.post("/step", response_model=dict)
async def add_step(
    request: AddStepRequest,
):
    """
    添加录制的步骤（由前端或录制代理调用）

    Args:
        request (AddStepRequest): 添加步骤请求

    Returns:
        dict: 操作结果
    """
    session = _recorder_sessions.get(request.session_id)
    if not session:
        raise HTTPException(status_code=404, detail=f"录制会话不存在: {request.session_id}")

    if not session.is_recording:
        raise HTTPException(status_code=400, detail="录制会话已停止")

    # 直接以 StepDefinition 格式存储（含 selectors/page_key/element_key/force/wait_after）
    step_data = request.step.model_dump(by_alias=False, exclude_none=True)
    session.steps.append(step_data)

    logger.info(f"步骤已添加: session_id={request.session_id}, step_order={step_data.get('order')}")
    return {
        "message": "步骤已添加",
        "step_order": step_data.get("order"),
        "total_steps": len(session.steps),
    }


@router.post("/assertion", response_model=dict)
async def add_assertion(
    request: AddAssertionRequest,
):
    """
    添加断言步骤

    Args:
        request (AddAssertionRequest): 添加断言请求

    Returns:
        dict: 操作结果
    """
    session = _recorder_sessions.get(request.session_id)
    if not session:
        raise HTTPException(status_code=404, detail=f"录制会话不存在: {request.session_id}")

    if not session.is_recording:
        raise HTTPException(status_code=400, detail="录制会话已停止")

    order = len(session.steps) + 1
    # 统一存储为 StepDefinition 格式：selectors 列表 + params
    step = {
        "order": order,
        "name": f"断言验证: {request.selector}",
        "action": "assert",
        "selectors": [request.selector] if request.selector else [],
        "params": {
            "selector": request.selector,
            "selectors": [request.selector] if request.selector else [],
            "type": request.type,
            "expected": request.expected or "",
        },
        "expected": request.expected or "",
    }
    session.steps.append(step)

    logger.info(f"断言已添加: session_id={request.session_id}, type={request.type}")
    return {
        "message": "断言步骤已添加",
        "step_order": order,
        "step": step,
    }


@router.post("/stop", response_model=dict)
async def stop_recording(
    session_id: str,
):
    """
    停止录制会话

    Args:
        session_id (str): 录制会话ID

    Returns:
        dict: 录制结果概要
    """
    session = _recorder_sessions.get(session_id)
    if not session:
        raise HTTPException(status_code=404, detail=f"录制会话不存在: {session_id}")

    session.is_recording = False

    logger.info(f"录制已停止: session_id={session_id}, total_steps={len(session.steps)}")
    return {
        "message": "录制已停止",
        "session_id": session_id,
        "total_steps": len(session.steps),
    }


@router.post("/save", response_model=dict)
async def save_as_case(
    request: SaveCaseRequest,
    db: AsyncSession = Depends(get_db),
):
    """
    保存为测试用例

    将录制会话中的步骤保存为测试用例。

    Args:
        request (SaveCaseRequest): 保存用例请求
        db (AsyncSession): 数据库会话

    Returns:
        dict: 保存的用例信息
    """
    session = _recorder_sessions.get(request.session_id)
    if not session:
        raise HTTPException(status_code=404, detail=f"录制会话不存在: {request.session_id}")

    if not session.steps:
        raise HTTPException(status_code=400, detail="没有可保存的步骤")

    try:
        case_service = CaseService(db)

        # 录制器已统一为 StepDefinition 格式（含 selectors/page_key/element_key/force/wait_after）
        # 直接透传给 CaseService，无需手动字段转换
        steps = []
        for s in session.steps:
            # 保留所有已知字段，未知字段原样放在 params 中
            step = {
                "order": s.get("order"),
                "name": s.get("name", ""),
                "action": s.get("action", ""),
                "params": s.get("params", {}),
                "expected": s.get("expected", ""),
            }
            # 透传可选字段（执行器识别）
            for opt_field in ("selectors", "page_key", "element_key", "force", "wait_after"):
                if opt_field in s and s[opt_field] is not None:
                    step[opt_field] = s[opt_field]
            steps.append(step)

        # 创建用例
        case_data = {
            "module_id": request.module_id or session.module_id,
            "name": request.name,
            "description": request.description or "",
            "priority": request.priority or "medium",
            "tags": request.tags or [],
            "steps": steps,
            "config": {
                "timeout": 30000,
                "screenshot": True,
                "step_retry_count": 2,
            },
        }

        case = await case_service.create_case(case_data)

        # 清理会话
        del _recorder_sessions[request.session_id]

        logger.info(f"录制保存为用例: case_id={case.id}, name={case.name}")
        return {
            "message": f"用例「{case.name}」保存成功",
            "case_id": case.id,
            "case_name": case.name,
            "steps_count": len(steps),
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"保存用例失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"保存用例失败: {str(e)}")