"""
MCP Server using official Python SDK (FastMCP)
"""
import os

# Set MCP default port before importing FastMCP to avoid conflicts with FastAPI
os.environ["FASTMCP_HOST"] = os.environ.get("FASTMCP_HOST", "127.0.0.1")
os.environ["FASTMCP_PORT"] = os.environ.get("FASTMCP_PORT", "8001")

import asyncio
import uuid
from datetime import datetime
from typing import List, Optional

import httpx
import yaml
from mcp.server.fastmcp import FastMCP
from pydantic import ValidationError

from opentester.core.storage import get_project_storage, get_template_storage
from opentester.models.dsl import DSLScript
from opentester.models.project import Target, TargetType, TestProject
from opentester.models.template import (
    TemplateCreate,
    TemplateUpdate,
    TemplateVariable,
    TemplateMetadata,
    TemplateCategory,
)

# API base URL for FastAPI server
API_BASE_URL = os.environ.get("OPENTESTER_API_URL", "http://127.0.0.1:8000")

mcp = FastMCP(
    "opentester",
    host=os.environ["FASTMCP_HOST"],
    port=int(os.environ["FASTMCP_PORT"]),
)
project_storage = get_project_storage()
template_storage = get_template_storage()


def _format_templates(templates: list) -> str:
    """Format template list as readable text."""
    if not templates:
        return "📋 暂无模板\n\n使用 `create_template` 创建新模板"
    lines = [f"📋 模板库 ({len(templates)} 个):\n"]
    for t in templates:
        vars_info = f" ({t.get('variable_count', 0)} 个变量)" if t.get('variable_count') else ""
        usage_info = f" [使用 {t.get('usage_count', 0)} 次]"
        lines.append(f"  • {t.get('name')}{vars_info}{usage_info}")
        lines.append(f"    ID: {t.get('id')}")
        lines.append(f"    类型: {t.get('target_type')} | 分类: {t.get('category', 'other')}")
        if t.get('description'):
            lines.append(f"    描述: {t.get('description')[:50]}{'...' if len(t.get('description', '')) > 50 else ''}")
        if t.get('tags'):
            lines.append(f"    标签: {', '.join(t.get('tags', []))}")
        lines.append("")
    return "\n".join(lines)


def _format_template_detail(template) -> str:
    """Format template details as readable text."""
    lines = [
        f"📄 模板: {template.name}",
        f"   ID: {template.id}",
        f"   类型: {template.target_type}",
        f"   分类: {template.metadata.category if template.metadata else 'other'}",
        f"   使用次数: {template.usage_count}",
        "",
    ]
    if template.description:
        lines.append(f"📝 描述:\n   {template.description}\n")

    if template.variables:
        lines.append("🔧 变量:")
        for var in template.variables:
            req = " (必填)" if var.required else ""
            default = f" [默认: {var.default_value}]" if var.default_value else ""
            lines.append(f"  • ${{{var.name}}}{req}{default}")
            if var.description:
                lines.append(f"    {var.description}")
    else:
        lines.append("🔧 变量: 无")

    if template.metadata and template.metadata.tags:
        lines.append(f"\n🏷️  标签: {', '.join(template.metadata.tags)}")

    lines.append(f"\n📅 创建: {template.created_at[:19] if template.created_at else 'N/A'}")
    lines.append(f"📅 更新: {template.updated_at[:19] if template.updated_at else 'N/A'}")

    lines.append("\n📋 DSL 模板内容:")
    lines.append("```yaml")
    lines.append(template.dsl_template[:500] + "..." if len(template.dsl_template) > 500 else template.dsl_template)
    lines.append("```")

    return "\n".join(lines)


def _format_projects(projects: list) -> str:
    """Format project list as readable text."""
    if not projects:
        return "暂无测试项目"
    lines = ["📋 测试项目列表：\n"]
    for p in projects:
        case_count = len(p.get("cases", {}))
        lines.append(f"  • {p.get('name')} ({p.get('target_type', 'cli')})")
        lines.append(f"    ID: {p.get('id')}")
        lines.append(f"    测试用例: {case_count} 个")
        lines.append(f"    更新时间: {p.get('updated_at', 'N/A')[:19] if p.get('updated_at') else 'N/A'}")
        lines.append("")
    return "\n".join(lines)


def _format_project_detail(project) -> str:
    """Format project details as readable text."""
    lines = [
        f"📁 项目: {project.name}",
        f"   ID: {project.id}",
        f"   类型: {project.target.type if project.target else 'cli'}",
        "",
        "📂 测试用例:"
    ]
    if project.cases:
        for case_id, case in project.cases.items():
            lines.append(f"  • {case.get('name', '未命名')} (ID: {case_id})")
    else:
        lines.append("  (暂无测试用例)")
    return "\n".join(lines)


def _format_validation_result(result: dict) -> str:
    """Format validation result as readable text."""
    if result.get("valid"):
        lines = ["✅ DSL 验证通过"]
        lines.append(f"   步骤数: {result.get('step_count', 0)}")
        if result.get("variables"):
            lines.append(f"   变量: {', '.join(result['variables'])}")
        return "\n".join(lines)
    else:
        return f"❌ DSL 验证失败\n   错误: {result.get('error', '未知错误')}"


def _format_execution_status(execution) -> str:
    """Format execution status as readable text."""
    status_emoji = {"completed": "✅", "running": "▶️", "stopped": "⏹️", "failed": "❌"}
    emoji = status_emoji.get(execution.status.value, "⏳")
    lines = [
        f"{emoji} 执行状态: {execution.status.value}",
        f"   ID: {execution.execution_id}",
        f"   进度: {execution.current_step}/{execution.total_steps} 步骤",
        ""
    ]
    if execution.results:
        lines.append("📊 执行结果:")
        for r in execution.results:
            result_emoji = "✅" if r.status.value == "passed" else "❌"
            lines.append(f"  {result_emoji} 步骤 {r.step_index + 1} [{r.action}]: {r.status.value}")
            if r.message:
                lines.append(f"     {r.message[:100]}{'...' if len(r.message) > 100 else ''}")
    return "\n".join(lines)


# ============== Project Tools ==============

@mcp.tool()
def list_projects() -> str:
    """List all test projects with basic info."""
    projects = project_storage.list_projects()
    return _format_projects(projects)


@mcp.tool()
def get_project(project_id: str) -> str:
    """Get project details including all test cases."""
    project = project_storage.get_project(project_id)
    if not project:
        return f"❌ 项目未找到: {project_id}"

    return _format_project_detail(project)


@mcp.tool()
def create_project(name: str, target_type: str) -> str:
    """Create a new test project."""
    try:
        project = TestProject(
            id=str(uuid.uuid4()),
            name=name,
            target=Target(type=target_type)
        )
        project_storage.save_project(project)
        return f"✅ 项目创建成功\n   名称: {name}\n   ID: {project.id}\n   类型: {target_type}"
    except Exception as e:
        return f"❌ 项目创建失败: {str(e)}"


@mcp.tool()
def delete_project(project_id: str) -> str:
    """Delete a project."""
    success = project_storage.delete_project(project_id)
    if success:
        return f"✅ 项目已删除: {project_id}"
    return f"❌ 删除失败: {project_id}"


# ============== DSL & Cases ==============

@mcp.tool()
def validate_dsl(dsl_yaml: str) -> str:
    """Validate DSL YAML syntax and schema."""
    if not dsl_yaml:
        return "❌ DSL 验证失败\n   错误: 输入为空"

    try:
        dsl_data = yaml.safe_load(dsl_yaml)
        script = DSLScript.model_validate(dsl_data)
        return _format_validation_result({
            "valid": True,
            "step_count": len(script.steps),
            "variables": list(script.vars.keys()) if script.vars else []
        })
    except yaml.YAMLError as e:
        return f"❌ DSL 验证失败\n   YAML 错误: {str(e)[:200]}"
    except ValidationError as e:
        return f"❌ DSL 验证失败\n   验证错误: {str(e)[:200]}"


@mcp.tool()
def save_case(project_id: str, name: str, dsl_yaml: str, case_id: str = "") -> str:
    """Save a test case."""
    case_id = case_id or str(uuid.uuid4())

    # Validate DSL
    try:
        dsl_data = yaml.safe_load(dsl_yaml)
        script = DSLScript.model_validate(dsl_data)
    except yaml.YAMLError as e:
        return f"❌ 保存失败\n   YAML 错误: {str(e)[:200]}"
    except ValidationError as e:
        return f"❌ 保存失败\n   验证错误: {str(e)[:200]}"

    project = project_storage.get_project(project_id)
    if not project:
        return f"❌ 保存失败\n   项目未找到: {project_id}"

    if not project.cases:
        project.cases = {}

    # Parse steps from DSL for storage
    steps = []
    for step_data in script.steps:
        steps.append(step_data.model_dump())

    now = datetime.now().isoformat()
    project.cases[case_id] = {
        "id": case_id,
        "name": name,
        "description": script.meta.get("description", ""),
        "dsl_content": dsl_yaml,
        "steps": steps,
        "execution_mode": "headless",
        "created_at": now,
        "updated_at": now
    }
    project_storage.save_project(project)

    return f"✅ 测试用例已保存\n   名称: {name}\n   ID: {case_id}\n   步骤数: {len(steps)}"


@mcp.tool()
def delete_case(project_id: str, case_id: str) -> str:
    """Delete a test case."""
    project = project_storage.get_project(project_id)
    if not project or not project.cases:
        return "❌ 删除失败\n   项目或测试用例未找到"

    if case_id in project.cases:
        case_name = project.cases[case_id].get("name", "未命名")
        del project.cases[case_id]
        project_storage.save_project(project)
        return f"✅ 测试用例已删除\n   名称: {case_name}\n   ID: {case_id}"

    return f"❌ 删除失败\n   测试用例未找到: {case_id}"


# ============== Execution ==============

@mcp.tool()
def run_case(project_id: str, case_id: str) -> str:
    """Execute a single test case via FastAPI to ensure execution is tracked."""
    project = project_storage.get_project(project_id)
    if not project:
        return f"❌ 启动失败\n   项目未找到: {project_id}"

    case = project.cases.get(case_id) if project.cases else None
    if not case:
        return f"❌ 启动失败\n   测试用例未找到: {case_id}"

    try:
        dsl_data = yaml.safe_load(case["dsl_content"])
        steps = dsl_data.get("steps", [])
    except Exception as e:
        return f"❌ 启动失败\n   DSL 解析错误: {str(e)[:200]}"

    # Call FastAPI via HTTP to create execution (ensures it's tracked in the main process)
    try:
        response = httpx.post(
            f"{API_BASE_URL}/api/execution/run",
            json={"project_id": project_id, "case_id": case_id},
            timeout=30.0
        )
        response.raise_for_status()
        result = response.json()
        execution_id = result.get("execution_id", "unknown")

        return (
            f"▶️ 测试已开始执行\n"
            f"   用例: {case.get('name', '未命名')}\n"
            f"   执行ID: {execution_id}\n"
            f"   步骤数: {len(steps)}\n"
            f"   监控: http://localhost:5173/executions/{execution_id}"
        )
    except httpx.HTTPError as e:
        return f"❌ 启动失败\n   API 调用错误: {str(e)[:200]}"
    except Exception as e:
        return f"❌ 启动失败\n   错误: {str(e)[:200]}"


@mcp.tool()
def run_project(project_id: str, case_ids: Optional[List[str]] = None) -> str:
    """Execute all test cases in a project via FastAPI to ensure executions are tracked."""
    project = project_storage.get_project(project_id)
    if not project or not project.cases:
        return "❌ 启动失败\n   项目或测试用例未找到"

    case_ids_to_run = case_ids or list(project.cases.keys())
    started_count = 0
    failed_count = 0
    lines = [f"▶️ 项目 '{project.name}' 开始执行", ""]

    for case_id in case_ids_to_run:
        case = project.cases.get(case_id)
        if not case:
            continue

        try:
            # Call FastAPI via HTTP to create execution for each case
            response = httpx.post(
                f"{API_BASE_URL}/api/execution/run",
                json={"project_id": project_id, "case_id": case_id},
                timeout=30.0
            )
            response.raise_for_status()
            result = response.json()
            execution_id = result.get("execution_id", "unknown")

            lines.append(f"  ✅ {case.get('name', '未命名')}")
            lines.append(f"     执行ID: {execution_id}")
            started_count += 1
        except Exception as e:
            lines.append(f"  ❌ {case.get('name', '未命名')}: {str(e)[:50]}")
            failed_count += 1

    lines.append("")
    lines.append(f"总计: {started_count} 个已开始, {failed_count} 个失败")
    return "\n".join(lines)


@mcp.tool()
def stop_execution(execution_id: str) -> str:
    """Stop a running execution via FastAPI."""
    try:
        response = httpx.post(
            f"{API_BASE_URL}/api/execution/stop/{execution_id}",
            timeout=10.0
        )
        response.raise_for_status()
        return f"⏹️ 执行已停止: {execution_id}"
    except httpx.HTTPError:
        return f"❌ 停止失败: {execution_id}"


@mcp.tool()
def get_execution_status(execution_id: str) -> str:
    """Get execution status and results via FastAPI."""
    try:
        response = httpx.get(
            f"{API_BASE_URL}/api/execution/status/{execution_id}",
            timeout=10.0
        )
        response.raise_for_status()
        result = response.json()

        status = result.get("status", "unknown")
        current_step = result.get("current_step", 0)
        total_steps = result.get("total_steps", 0)
        message = result.get("message", "")

        status_emoji = {"completed": "✅", "running": "▶️", "stopped": "⏹️", "failed": "❌", "pending": "⏳"}
        emoji = status_emoji.get(status, "⏳")

        lines = [
            f"{emoji} 执行状态: {status}",
            f"   ID: {execution_id}",
            f"   进度: {current_step}/{total_steps} 步骤",
        ]
        if message:
            lines.append(f"   消息: {message}")
        return "\n".join(lines)
    except httpx.HTTPError:
        return f"❌ 执行未找到: {execution_id}"


@mcp.tool()
def get_execution_log(execution_id: str) -> str:
    """Get detailed execution log via FastAPI."""
    try:
        response = httpx.get(
            f"{API_BASE_URL}/api/execution/{execution_id}/results",
            timeout=10.0
        )
        response.raise_for_status()
        result = response.json()

        status = result.get("status", "unknown")
        current_step = result.get("current_step", 0)
        total_steps = result.get("total_steps", 0)
        results = result.get("results", [])

        status_emoji = {"completed": "✅", "running": "▶️", "stopped": "⏹️", "failed": "❌", "pending": "⏳"}
        emoji = status_emoji.get(status, "⏳")

        lines = [
            f"{emoji} 执行详情: {execution_id}",
            f"   状态: {status}",
            f"   进度: {current_step}/{total_steps} 步骤",
            "",
            "📋 详细日志:"
        ]

        for r in results:
            step_status = r.get("status", "unknown")
            result_emoji = "✅" if step_status == "passed" else "❌"
            lines.append(f"\n  步骤 {r.get('step_index', 0) + 1} [{r.get('action', 'unknown')}] {result_emoji}")
            lines.append(f"  状态: {step_status}")
            if r.get("message"):
                lines.append(f"  消息: {r['message']}")
            if r.get("duration_ms"):
                lines.append(f"  耗时: {r['duration_ms']}ms")

        return "\n".join(lines)
    except httpx.HTTPError:
        return f"❌ 执行未找到: {execution_id}"


# ============== Template Tools ==============

@mcp.tool()
def list_templates(
    category: str = "",
    target_type: str = "",
    tag: str = "",
) -> str:
    """List all available templates with optional filtering.

    Args:
        category: Filter by category (cli/api/tui/gui/auth/database/workflow/other)
        target_type: Filter by target type (cli/api/tui/gui)
        tag: Filter by tag
    """
    templates = template_storage.list_templates(
        category=category if category else None,
        target_type=target_type if target_type else None,
        tag=tag if tag else None,
    )
    return _format_templates(templates)


@mcp.tool()
def get_template(template_id: str) -> str:
    """Get template details including variables and DSL content."""
    template = template_storage.get_template(template_id)
    if not template:
        return f"❌ 模板未找到: {template_id}"
    return _format_template_detail(template)


@mcp.tool()
def create_template(
    name: str,
    dsl_template: str,
    description: str = "",
    target_type: str = "cli",
    category: str = "other",
    tags: list = None,
) -> str:
    """Create a new reusable DSL template.

    Args:
        name: Template name
        dsl_template: DSL YAML template with ${variable} placeholders
        description: Template description
        target_type: Target type (cli/api/tui/gui)
        category: Template category
        tags: List of tags

    Example DSL template:
        version: "1.0"
        meta:
          name: "${test_name}"
        target:
          type: ${target_type}
        steps:
          - name: "Execute command"
            action: exec
            command: "${command}"
    """
    try:
        # Extract variables from DSL template
        import re
        var_pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
        var_names = set(re.findall(var_pattern, dsl_template))

        variables = []
        for var_name in var_names:
            variables.append(TemplateVariable(
                name=var_name,
                description=f"Variable {var_name}",
                required=True,
            ))

        metadata = TemplateMetadata(
            category=category if category else "other",
            tags=tags or [],
        )

        template_data = TemplateCreate(
            name=name,
            description=description,
            target_type=target_type,
            dsl_template=dsl_template,
            variables=variables,
            metadata=metadata,
        )

        template = template_storage.create_template(template_data)
        return (
            f"✅ 模板创建成功\n"
            f"   名称: {template.name}\n"
            f"   ID: {template.id}\n"
            f"   类型: {template.target_type}\n"
            f"   变量: {len(template.variables)} 个"
        )
    except Exception as e:
        return f"❌ 模板创建失败: {str(e)[:200]}"


@mcp.tool()
def update_template(
    template_id: str,
    name: str = "",
    description: str = "",
    dsl_template: str = "",
    category: str = "",
    tags: list = None,
) -> str:
    """Update an existing template."""
    template = template_storage.get_template(template_id)
    if not template:
        return f"❌ 模板未找到: {template_id}"

    try:
        # Re-extract variables if DSL changed
        variables = None
        if dsl_template:
            import re
            var_pattern = r'\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}'
            var_names = set(re.findall(var_pattern, dsl_template))
            variables = [
                TemplateVariable(name=v, description=f"Variable {v}", required=True)
                for v in var_names
            ]

        metadata = None
        if category or tags is not None:
            metadata = TemplateMetadata(
                category=category if category else (template.metadata.category if template.metadata else "other"),
                tags=tags if tags is not None else (template.metadata.tags if template.metadata else []),
            )

        update_data = TemplateUpdate(
            name=name if name else None,
            description=description if description else None,
            dsl_template=dsl_template if dsl_template else None,
            variables=variables,
            metadata=metadata,
        )

        updated = template_storage.update_template(template_id, update_data)
        if updated:
            return (
                f"✅ 模板更新成功\n"
                f"   名称: {updated.name}\n"
                f"   ID: {updated.id}"
            )
        return "❌ 更新失败"
    except Exception as e:
        return f"❌ 模板更新失败: {str(e)[:200]}"


@mcp.tool()
def delete_template(template_id: str) -> str:
    """Delete a template."""
    template = template_storage.get_template(template_id)
    if not template:
        return f"❌ 模板未找到: {template_id}"

    success = template_storage.delete_template(template_id)
    if success:
        return f"✅ 模板已删除: {template.name}"
    return f"❌ 删除失败: {template_id}"


@mcp.tool()
def instantiate_template(
    template_id: str,
    project_id: str,
    case_name: str,
    variables: dict = None,
) -> str:
    """Instantiate a template into a test case.

    Args:
        template_id: Template ID
        project_id: Target project ID
        case_name: Name for the new test case
        variables: Dictionary of variable values {"var_name": "value"}
    """
    template = template_storage.get_template(template_id)
    if not template:
        return f"❌ 模板未找到: {template_id}"

    project = project_storage.get_project(project_id)
    if not project:
        return f"❌ 项目未找到: {project_id}"

    try:
        # Instantiate template with variables
        dsl_yaml = template_storage.instantiate_template(
            template_id,
            variables or {}
        )

        if not dsl_yaml:
            return "❌ 模板实例化失败"

        # Validate the instantiated DSL
        try:
            dsl_data = yaml.safe_load(dsl_yaml)
            script = DSLScript.model_validate(dsl_data)
        except yaml.YAMLError as e:
            return f"❌ DSL YAML 错误: {str(e)[:200]}"
        except ValidationError as e:
            return f"❌ DSL 验证错误: {str(e)[:200]}"

        # Save as test case
        case_id = str(uuid.uuid4())
        now = datetime.now().isoformat()

        if not project.cases:
            project.cases = {}

        steps = [step_data.model_dump() for step_data in script.steps]

        project.cases[case_id] = {
            "id": case_id,
            "name": case_name,
            "description": f"从模板 '{template.name}' 创建",
            "dsl_content": dsl_yaml,
            "steps": steps,
            "execution_mode": "headless",
            "created_at": now,
            "updated_at": now,
            "template_id": template_id,
        }
        project_storage.save_project(project)

        return (
            f"✅ 模板实例化成功\n"
            f"   模板: {template.name}\n"
            f"   用例: {case_name}\n"
            f"   ID: {case_id}\n"
            f"   步骤数: {len(steps)}"
        )
    except ValueError as e:
        return f"❌ 变量错误: {str(e)}"
    except Exception as e:
        return f"❌ 实例化失败: {str(e)[:200]}"


@mcp.tool()
def create_template_from_case(
    project_id: str,
    case_id: str,
    template_name: str,
    description: str = "",
    category: str = "other",
    variable_mappings: dict = None,
) -> str:
    """Create a template from an existing test case.

    Args:
        project_id: Project ID
        case_id: Case ID to convert
        template_name: Name for the new template
        description: Template description
        category: Template category
        variable_mappings: Dict mapping {"original_value": "var_name"}
                          Values will be replaced with ${var_name} placeholders
    """
    project = project_storage.get_project(project_id)
    if not project:
        return f"❌ 项目未找到: {project_id}"

    case = project.cases.get(case_id) if project.cases else None
    if not case:
        return f"❌ 测试用例未找到: {case_id}"

    try:
        dsl_content = case.get("dsl_content", "")
        if not dsl_content:
            return "❌ 测试用例没有 DSL 内容"

        # Replace specified values with variables
        variables = []
        if variable_mappings:
            for original, var_name in variable_mappings.items():
                if original in dsl_content:
                    dsl_content = dsl_content.replace(original, f"${{{var_name}}}")
                    variables.append(TemplateVariable(
                        name=var_name,
                        description=f"Replaces '{original[:30]}...'" if len(original) > 30 else f"Replaces '{original}'",
                        required=True,
                    ))

        metadata = TemplateMetadata(
            category=category,
            tags=["from-case", project.name],
        )

        template_data = TemplateCreate(
            name=template_name,
            description=description or f"从用例 '{case.get('name')}' 创建的模板",
            target_type=project.target.type if project.target else "cli",
            dsl_template=dsl_content,
            variables=variables,
            metadata=metadata,
        )

        template = template_storage.create_template(template_data)
        return (
            f"✅ 模板创建成功\n"
            f"   名称: {template.name}\n"
            f"   ID: {template.id}\n"
            f"   变量: {len(template.variables)} 个\n"
            f"   来源: {case.get('name')}"
        )
    except Exception as e:
        return f"❌ 模板创建失败: {str(e)[:200]}"


# ============== Entry Point ==============

def main():
    """Run the MCP server with Streamable HTTP transport (no SSE)."""
    import argparse

    parser = argparse.ArgumentParser(description="OpenTester MCP Server")
    parser.add_argument("--host", default="127.0.0.1", help="Host (default: 127.0.0.1)")
    parser.add_argument("--port", type=int, default=8001, help="Port (default: 8001)")
    args = parser.parse_args()

    # Set environment for MCP SDK
    os.environ["FASTMCP_HOST"] = args.host
    os.environ["FASTMCP_PORT"] = str(args.port)

    print(f"Starting MCP Server: http://{args.host}:{args.port}/mcp")
    print(f"  - Transport: Streamable HTTP (no SSE)")

    # Use Streamable HTTP transport only
    mcp.run(transport="streamable-http")


if __name__ == "__main__":
    main()
