#!/usr/bin/env python3
"""禅道 MCP Server - 任务与工时管理"""

import asyncio
import os
import csv
import logging
from io import StringIO
from datetime import date, datetime, timedelta
from typing import Any
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import httpx

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-zentao")

server = Server("mcp-zentao")

# ─── 配置 ───

ZENTAO_HOST = os.environ.get("ZENTAO_HOST", "").rstrip("/")
ZENTAO_ACCOUNT = os.environ.get("ZENTAO_ACCOUNT", "")
ZENTAO_PASSWORD = os.environ.get("ZENTAO_PASSWORD", "")
DEFAULT_EXECUTION = os.environ.get("ZENTAO_DEFAULT_EXECUTION_NAME", "")
DEFAULT_ASSIGNEE = os.environ.get("ZENTAO_DEFAULT_ASSIGNEE", "") or ZENTAO_ACCOUNT
DEFAULT_TASK_TYPE = os.environ.get("ZENTAO_DEFAULT_TASK_TYPE", "devel")

# 启动时校验必要配置
_REQUIRED_ENV = ["ZENTAO_HOST", "ZENTAO_ACCOUNT", "ZENTAO_PASSWORD", "ZENTAO_DEFAULT_EXECUTION_NAME"]
_missing = [k for k in _REQUIRED_ENV if not os.environ.get(k)]
if _missing:
    logger.warning(f"缺少环境变量: {', '.join(_missing)}，部分功能可能无法正常工作")

# ─── API 客户端 ───

_token: str | None = None
_client: httpx.Client | None = None


def get_client() -> httpx.Client:
    global _client
    if _client is None or _client.is_closed:
        _client = httpx.Client(timeout=30.0, verify=False)
    return _client


def get_api_base() -> str:
    return f"{ZENTAO_HOST}/api.php/v1"


def login() -> str:
    global _token
    resp = get_client().post(
        f"{get_api_base()}/tokens",
        json={"account": ZENTAO_ACCOUNT, "password": ZENTAO_PASSWORD},
    )
    data = resp.json()
    _token = data.get("token")
    if not _token:
        raise Exception(f"登录失败: {data}")
    return _token


def api_request(method: str, endpoint: str, **kwargs) -> dict | list:
    """发送 API 请求，自动处理认证和重试"""
    global _token
    if not _token:
        login()

    url = f"{get_api_base()}/{endpoint.lstrip('/')}"
    headers = kwargs.pop("headers", {})
    headers["Token"] = _token

    resp = get_client().request(method, url, headers=headers, **kwargs)

    # token 过期，重新登录重试
    if resp.status_code == 401:
        login()
        headers["Token"] = _token
        resp = get_client().request(method, url, headers=headers, **kwargs)

    if resp.status_code >= 400:
        raise Exception(f"API {resp.status_code}: {resp.text[:300]}")

    if not resp.text or resp.headers.get("content-length") == "0":
        return {}
    try:
        return resp.json()
    except Exception:
        logger.warning(f"API 响应非 JSON: {resp.text[:200]}")
        return {}


# ─── 业务逻辑 ───

_execution_cache: dict[str, int] = {}


def find_execution_id(name: str) -> int:
    """根据名称查找执行 ID，带缓存"""
    if name in _execution_cache:
        return _execution_cache[name]
    data = api_request("GET", "executions?status=all&limit=1000")
    for ex in data.get("executions", []):
        if ex["name"] == name:
            _execution_cache[name] = ex["id"]
            return ex["id"]
    raise Exception(f"未找到执行: {name}")


def create_task(args: dict) -> str:
    execution_name = DEFAULT_EXECUTION
    execution_id = find_execution_id(execution_name)

    today = date.today().isoformat()
    assigned_to = args.get("assigned_to") or DEFAULT_ASSIGNEE

    # 组装描述
    desc_parts = [
        f"【任务描述】\n{args['description']}",
        f"【工作内容】\n{args['work_content']}",
        f"【验收标准】\n{args['acceptance_criteria']}",
    ]
    if args.get("completion_note"):
        desc_parts.append(f"【完成说明】\n{args['completion_note']}")
    full_desc = "\n\n".join(desc_parts)

    data = {
        "name": args["title"],
        "type": DEFAULT_TASK_TYPE,
        "assignedTo": assigned_to,
        "estStarted": args.get("start_date") or today,
        "deadline": args.get("end_date") or today,
        "desc": full_desc,
    }
    if args.get("est_hours") and args["est_hours"] > 0:
        data["estimate"] = args["est_hours"]

    result = api_request("POST", f"executions/{execution_id}/tasks", json=data)
    task_id = result.get("id", "未知")

    # 默认自动完成任务（大多数场景是记录已完成的工作）
    auto_finish = args.get("auto_finish", True)
    status_text = "未开始"
    if auto_finish and task_id != "未知":
        try:
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            est = args.get("est_hours", 0) or 0
            left = est if est > 0 else 1

            # 先激活（wait -> doing）
            start_resp = api_request("POST", f"tasks/{task_id}/start", json={
                "assignedTo": assigned_to,
                "realStarted": now,
                "left": left,
            })
            logger.info(f"start 响应: {start_resp}")

            # 再完成（doing -> done）
            finish_resp = api_request("POST", f"tasks/{task_id}/finish", json={
                "assignedTo": assigned_to,
                "currentConsumed": est,
                "realStarted": now,
                "finishedDate": now,
            })
            logger.info(f"finish 响应: {finish_resp}")
            status_text = "已完成"
        except Exception as e:
            logger.error(f"自动完成失败: {e}")
            status_text = f"已创建但自动完成失败: {e}"

    return (
        f"任务创建成功\n"
        f"  ID: {task_id}\n"
        f"  标题: {args['title']}\n"
        f"  执行: {execution_name}\n"
        f"  负责人: {assigned_to}\n"
        f"  日期: {data['estStarted']} ~ {data['deadline']}\n"
        f"  状态: {status_text}"
    )


def record_effort(args: dict) -> str:
    task_id = args["task_id"]
    effort_date = args.get("date") or date.today().isoformat()

    data = {
        "consumed": args["hours"],
        "work": args["work"],
        "date": effort_date,
        "left": 0,  # 已完成任务记录工时，剩余默认为 0
    }
    api_request("POST", f"tasks/{task_id}/estimate", json=data)

    return (
        f"工时记录成功\n"
        f"  任务 ID: {task_id}\n"
        f"  日期: {effort_date}\n"
        f"  工时: {args['hours']} 小时\n"
        f"  内容: {args['work']}"
    )


def list_tasks(args: dict) -> str:
    execution_name = args.get("execution_name") or DEFAULT_EXECUTION
    execution_id = find_execution_id(execution_name)
    assigned_to = args.get("assigned_to") or ZENTAO_ACCOUNT

    params = [f"limit=1000", f"assignedTo={assigned_to}"]
    if args.get("status"):
        params.append(f"status={args['status']}")
    query = "&".join(params)

    data = api_request("GET", f"executions/{execution_id}/tasks?{query}")
    tasks = data.get("tasks", [])

    # 日期过滤（同时考虑 estStarted 和 deadline，任一在范围内即匹配）
    start_filter = args.get("start_date")
    end_filter = args.get("end_date")
    if start_filter or end_filter:
        filtered = []
        for t in tasks:
            est = t.get("estStarted") or ""
            dl = t.get("deadline") or ""
            # 取任务的有效日期范围
            task_start = est or dl
            task_end = dl or est
            if not task_start:
                continue
            if start_filter and task_end < start_filter:
                continue
            if end_filter and task_start > end_filter:
                continue
            filtered.append(t)
        tasks = filtered

    if not tasks:
        return "没有找到任务"

    status_map = {
        "wait": "未开始", "doing": "进行中", "done": "已完成",
        "closed": "已关闭", "cancel": "已取消", "pause": "已暂停",
    }

    lines = [f"共 {len(tasks)} 个任务:\n"]
    for t in tasks:
        tid = t.get("id", "")
        name = t.get("name", "")
        status = status_map.get(t.get("status", ""), t.get("status", ""))
        who = t.get("assignedTo", "")
        if isinstance(who, dict):
            who = who.get("realname") or who.get("account", "")
        est = t.get("estStarted", "")
        dl = t.get("deadline", "")
        lines.append(f"- [{tid}] {name}\n  状态: {status} | 负责人: {who} | 时间: {est} ~ {dl}")

    return "\n".join(lines)


def get_task(args: dict) -> str:
    task_id = args["task_id"]
    t = api_request("GET", f"tasks/{task_id}")

    status_map = {
        "wait": "未开始", "doing": "进行中", "done": "已完成",
        "closed": "已关闭", "cancel": "已取消", "pause": "已暂停",
    }

    who = t.get("assignedTo", "")
    if isinstance(who, dict):
        who = who.get("realname") or who.get("account", "")

    lines = [
        f"任务详情:",
        f"  ID: {t.get('id', '')}",
        f"  名称: {t.get('name', '')}",
        f"  状态: {status_map.get(t.get('status', ''), t.get('status', ''))}",
        f"  负责人: {who}",
        f"  开始日期: {t.get('estStarted', '')}",
        f"  截止日期: {t.get('deadline', '')}",
        f"  预计工时: {t.get('estimate', 0)} 小时",
        f"  已消耗: {t.get('consumed', 0)} 小时",
        f"  剩余: {t.get('left', 0)} 小时",
    ]
    if t.get("desc"):
        lines.append(f"\n描述:\n{t['desc']}")

    return "\n".join(lines)


def edit_task(args: dict) -> str:
    task_id = args["task_id"]
    data = {}
    results = []

    # 状态变更需要走专用接口
    new_status = args.get("status")
    if new_status:
        assigned_to = args.get("assigned_to") or DEFAULT_ASSIGNEE
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if new_status == "doing":
            est = args.get("est_hours", 0) or 1
            api_request("POST", f"tasks/{task_id}/start", json={
                "assignedTo": assigned_to, "realStarted": now, "left": est,
            })
            results.append("状态 → 进行中")
        elif new_status == "done":
            # 先确保任务在 doing 状态
            task_info = api_request("GET", f"tasks/{task_id}")
            if task_info.get("status") == "wait":
                api_request("POST", f"tasks/{task_id}/start", json={
                    "assignedTo": assigned_to, "realStarted": now, "left": 1,
                })
            consumed = args.get("est_hours", 0) or 0
            api_request("POST", f"tasks/{task_id}/finish", json={
                "assignedTo": assigned_to, "currentConsumed": consumed,
                "realStarted": now, "finishedDate": now,
            })
            results.append("状态 → 已完成")
        elif new_status == "closed":
            api_request("POST", f"tasks/{task_id}/close", json={})
            results.append("状态 → 已关闭")
        else:
            data["status"] = new_status

    # 其他字段走 PUT 更新
    field_map = {
        "title": "name",
        "assigned_to": "assignedTo",
        "start_date": "estStarted",
        "end_date": "deadline",
        "est_hours": "estimate",
        "description": "desc",
    }
    for arg_key, api_key in field_map.items():
        if arg_key in args and args[arg_key] is not None:
            data[api_key] = args[arg_key]

    if data:
        api_request("PUT", f"tasks/{task_id}", json=data)
        updated = ", ".join(f"{k}={v}" for k, v in data.items())
        results.append(f"字段更新: {updated}")

    if not results:
        return "没有需要更新的字段"

    return f"任务更新成功\n  ID: {task_id}\n  {'; '.join(results)}"


def delete_task(args: dict) -> str:
    task_id = args["task_id"]
    api_request("DELETE", f"tasks/{task_id}")
    return f"任务删除成功\n  ID: {task_id}"


def generate_6r_report(args: dict) -> str:
    assigned_to = args.get("assigned_to") or ZENTAO_ACCOUNT
    start_date = args["start_date"]
    end_date = args["end_date"]
    department = args.get("department", "")

    # 获取所有执行下的任务（遍历所有执行太慢，只查默认执行）
    execution_id = find_execution_id(DEFAULT_EXECUTION)
    data = api_request("GET", f"executions/{execution_id}/tasks?limit=1000&assignedTo={assigned_to}")
    tasks = data.get("tasks", [])

    # 日期过滤
    tasks = [t for t in tasks if start_date <= (t.get("estStarted") or "") <= end_date]

    # 计算周三
    start_dt = datetime.strptime(start_date, "%Y-%m-%d")
    end_dt = datetime.strptime(end_date, "%Y-%m-%d")
    wednesdays = []
    cur = start_dt
    while cur <= end_dt:
        if cur.weekday() == 2:
            wednesdays.append(cur.strftime("%Y-%m-%d"))
        cur += timedelta(days=1)
    wed1 = wednesdays[0] if len(wednesdays) > 0 else ""
    wed2 = wednesdays[1] if len(wednesdays) > 1 else ""

    # 生成 CSV
    output = StringIO()
    writer = csv.writer(output)
    writer.writerow([f"6R信息中心6R结果管控表（{start_date} - {end_date}）"])
    writer.writerow([])
    writer.writerow([
        "部门/项目组", "责任人", "目标设定", "禅道任务链接",
        "开始日期", "完成日期", "第1个周三", "第2个周三",
        "主管节点检查打卡日", "完成结果", "上级核实结果", "未完成复盘",
    ])

    for t in tasks:
        tid = t.get("id", "")
        who = t.get("assignedTo", "")
        if isinstance(who, dict):
            who = who.get("realname") or who.get("account", "")
        is_done = "Y" if t.get("status") in ("done", "closed") else "N"
        link = f"{ZENTAO_HOST}/execution-task-view-{tid}.html" if tid else ""
        writer.writerow([
            department, who, t.get("name", ""), link,
            t.get("estStarted", ""), t.get("deadline", ""),
            wed1, wed2, "", is_done, "",
            "" if is_done == "Y" else "待复盘",
        ])

    return f"6R 报表（{start_date} ~ {end_date}，{len(tasks)} 个任务）:\n\n{output.getvalue()}"


# ─── MCP 工具定义 ───

TOOLS = [
    Tool(
        name="zentao_create_task",
        description="""在禅道指定执行下创建结构化任务。
            
重要：在调用此工具前，你必须先将用户的口语化描述整理为规范的禅道任务格式。

你的角色：资深企业运维/基础设施负责人，熟悉禅道任务管理。

整理原则：
1. 语言专业、克制、偏事实描述，不使用口语
2. 明确区分【任务标题/任务描述/工作内容/验收标准/完成说明】
3. 描述聚焦"做了什么 + 交付了什么"，避免空话
4. 不主动扩大范围，不替用户承诺时间或结果
5. 默认场景为企业运维/研发协作环境

如果不指定日期，默认使用今天。""",
        inputSchema={
            "type": "object",
            "properties": {
                "title": {"type": "string", "description": "任务标题"},
                "description": {"type": "string", "description": "任务描述（背景、目的）"},
                "work_content": {"type": "string", "description": "工作内容（具体步骤）"},
                "acceptance_criteria": {"type": "string", "description": "验收标准（可量化的完成标准）"},
                "completion_note": {"type": ["string", "null"], "default": "", "description": "完成说明（可选，任务完成后填写）"},
                "est_hours": {"type": ["number", "null"], "default": 0, "description": "预计工时（小时）"},
                "start_date": {"type": ["string", "null"], "default": None, "description": "开始日期 YYYY-MM-DD，默认今天"},
                "end_date": {"type": ["string", "null"], "default": None, "description": "结束日期 YYYY-MM-DD，默认今天"},
                "assigned_to": {"type": ["string", "null"], "default": None, "description": "指派给谁，默认配置的用户"},
                "auto_finish": {"type": "boolean", "default": True, "description": "是否自动完成任务（默认 true，适用于记录已完成的工作）"},
            },
            "required": ["title", "description", "work_content", "acceptance_criteria"],
        },
    ),
    Tool(
        name="zentao_record_effort",
        description="记录任务工时。如果不指定日期，默认记录今天的工时。",
        inputSchema={
            "type": "object",
            "properties": {
                "task_id": {"type": "integer", "description": "任务 ID"},
                "hours": {"type": "number", "description": "工时（小时）"},
                "work": {"type": "string", "description": "工作内容描述"},
                "date": {"type": ["string", "null"], "default": None, "description": "日期 YYYY-MM-DD，默认今天"},
            },
            "required": ["task_id", "hours", "work"],
        },
    ),
    Tool(
        name="zentao_list_tasks",
        description="查询任务列表。可以按执行、负责人、状态、日期范围筛选。",
        inputSchema={
            "type": "object",
            "properties": {
                "execution_name": {"type": ["string", "null"], "default": None, "description": "执行名称，默认使用配置的执行"},
                "assigned_to": {"type": ["string", "null"], "default": None, "description": "指派人，默认当前用户"},
                "status": {"type": ["string", "null"], "default": None, "description": "任务状态: wait/doing/done/closed"},
                "start_date": {"type": ["string", "null"], "default": None, "description": "开始日期 YYYY-MM-DD"},
                "end_date": {"type": ["string", "null"], "default": None, "description": "结束日期 YYYY-MM-DD"},
            },
        },
    ),
    Tool(
        name="zentao_get_task",
        description="获取任务详细信息。",
        inputSchema={
            "type": "object",
            "properties": {
                "task_id": {"type": "integer", "description": "任务 ID"},
            },
            "required": ["task_id"],
        },
    ),
    Tool(
        name="zentao_edit_task",
        description="编辑/更新禅道任务。只需传入要修改的字段，未传入的字段保持不变。",
        inputSchema={
            "type": "object",
            "properties": {
                "task_id": {"type": "integer", "description": "任务 ID"},
                "title": {"type": ["string", "null"], "default": None, "description": "任务标题"},
                "description": {"type": ["string", "null"], "default": None, "description": "任务描述"},
                "assigned_to": {"type": ["string", "null"], "default": None, "description": "指派给谁"},
                "start_date": {"type": ["string", "null"], "default": None, "description": "开始日期 YYYY-MM-DD"},
                "end_date": {"type": ["string", "null"], "default": None, "description": "截止日期 YYYY-MM-DD"},
                "est_hours": {"type": ["number", "null"], "default": None, "description": "预计工时（小时）"},
                "status": {"type": ["string", "null"], "default": None, "description": "状态: wait/doing/done/closed"},
            },
            "required": ["task_id"],
        },
    ),
    Tool(
        name="zentao_delete_task",
        description="删除禅道任务。此操作不可恢复，请谨慎使用。",
        inputSchema={
            "type": "object",
            "properties": {
                "task_id": {"type": "integer", "description": "任务 ID"},
            },
            "required": ["task_id"],
        },
    ),
    Tool(
        name="zentao_generate_6r_report",
        description="生成指定时间范围的 6R 格式报表（CSV 格式）。",
        inputSchema={
            "type": "object",
            "properties": {
                "start_date": {"type": "string", "description": "开始日期 YYYY-MM-DD"},
                "end_date": {"type": "string", "description": "结束日期 YYYY-MM-DD"},
                "assigned_to": {"type": ["string", "null"], "default": None, "description": "指派人，默认当前用户"},
                "department": {"type": ["string", "null"], "default": "", "description": "部门/项目组名称"},
            },
            "required": ["start_date", "end_date"],
        },
    ),
]


@server.list_tools()
async def list_tools() -> list[Tool]:
    return TOOLS


@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
    handlers = {
        "zentao_create_task": create_task,
        "zentao_record_effort": record_effort,
        "zentao_list_tasks": list_tasks,
        "zentao_get_task": get_task,
        "zentao_edit_task": edit_task,
        "zentao_delete_task": delete_task,
        "zentao_generate_6r_report": generate_6r_report,
    }
    handler = handlers.get(name)
    if not handler:
        return [TextContent(type="text", text=f"未知工具: {name}")]
    try:
        result = handler(arguments)
        return [TextContent(type="text", text=result)]
    except Exception as e:
        logger.error(f"工具 {name} 执行失败: {e}")
        return [TextContent(type="text", text=f"错误: {e}")]


async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())


def main_sync():
    """同步入口点，供 pyproject.toml scripts 使用"""
    asyncio.run(main())


if __name__ == "__main__":
    main_sync()
