# -*- coding: utf-8 -*-
"""
后台 Job 执行追踪包装器

提供 trace_job() 包装函数，在 lifespan 注册的 job handler 外层包裹，
追踪后台任务执行全过程（JOB_START → 内部 span → JOB_END / JOB_ERROR）。

Job trace 使用独立的 request_id（job_ 前缀），写入同一日志文件。
内部的 SERVICE / DB_QUERY span 通过 contextvars 自动关联到 job trace。
"""

from __future__ import annotations

import functools
import time
import traceback
from datetime import datetime
from typing import Any, Callable

from app.trace.config import get_trace_config
from app.trace.context import (
    SpanType,
    TraceSpan,
    create_job_trace,
    set_current_trace,
    trace_context_var,
)
from app.trace.writer import TraceWriter


def trace_job(job_name: str) -> Callable:
    """装饰器：包装 job handler，追踪后台任务执行。

    用法::

        @trace_job("task_generator")
        def my_handler(**kwargs):
            ...

    或在 register_job 时包装::

        register_job("task_generator", trace_job("task_generator")(lambda **_kw: task_generator.run()))
    """

    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            cfg = get_trace_config()
            if not cfg.enabled:
                return func(*args, **kwargs)

            # 创建 job trace 上下文
            ctx = create_job_trace(job_name)
            token = set_current_trace(ctx)

            # JOB_START span
            ctx.add_span(TraceSpan(
                span_type=SpanType.JOB_START,
                module="trace.job_wrapper",
                function=f"trace_job({job_name})",
                description_zh=f"后台任务开始: {job_name}",
                description_en=f"Background job started: {job_name}",
                params={"job_name": job_name},
                result_summary="",
                duration_ms=0.0,
                timestamp=datetime.now().isoformat(),
            ))

            t0 = time.perf_counter()
            try:
                result = func(*args, **kwargs)
                duration_ms = (time.perf_counter() - t0) * 1000

                # JOB_END span
                ctx.add_span(TraceSpan(
                    span_type=SpanType.JOB_END,
                    module="trace.job_wrapper",
                    function=f"trace_job({job_name})",
                    description_zh=f"后台任务完成: {job_name}, 耗时={duration_ms:.0f}ms",
                    description_en=f"Background job completed: {job_name}, duration={duration_ms:.0f}ms",
                    params={"job_name": job_name},
                    result_summary=f"completed in {duration_ms:.0f}ms",
                    duration_ms=duration_ms,
                    timestamp=datetime.now().isoformat(),
                ))
                return result

            except Exception as exc:
                duration_ms = (time.perf_counter() - t0) * 1000
                tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
                stack_summary = "".join(tb_lines[-5:])

                # JOB_ERROR span
                ctx.add_span(TraceSpan(
                    span_type=SpanType.JOB_ERROR,
                    module="trace.job_wrapper",
                    function=f"trace_job({job_name})",
                    description_zh=f"后台任务异常: {job_name}, {type(exc).__name__}: {exc}",
                    description_en=f"Background job error: {job_name}, {type(exc).__name__}: {exc}",
                    params={"job_name": job_name, "error_type": type(exc).__name__},
                    result_summary=f"{type(exc).__name__}: {exc}",
                    duration_ms=duration_ms,
                    timestamp=datetime.now().isoformat(),
                    extra={"stack_summary": stack_summary},
                ))
                raise
            finally:
                # 同步写入日志文件（job handler 在同步上下文中执行）
                try:
                    import json
                    from app.trace.writer import serialize_trace, get_log_file

                    writer = TraceWriter()
                    data = serialize_trace(ctx)
                    line = json.dumps(data, ensure_ascii=False, default=str)
                    dt = ctx.start_time if isinstance(ctx.start_time, datetime) else datetime.now()
                    filepath = get_log_file(dt, base_dir=writer.base_dir)
                    filepath.parent.mkdir(parents=True, exist_ok=True)
                    filepath = writer._rotate_if_needed(filepath)
                    writer._sync_write(filepath, line)
                except Exception:
                    pass  # 写入失败不影响业务
                # 恢复 contextvars
                trace_context_var.set(None)

        return wrapper
    return decorator
