"""Manual query batch submission API."""
from __future__ import annotations

import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any

from fastapi import APIRouter, Body, Depends, HTTPException
from pydantic import BaseModel, Field

from acquisition.repositories.postgres import PostgresAcquisitionRepository
from app.dependencies import _env_file, get_creation_db_config
from app.routes.acquisition import _model_dump
from core.config import CreationDbConfig
from core.db_session import transaction

router = APIRouter(prefix="/api/query-batches", tags=["manual-query-batches"])

ROOT = Path(__file__).resolve().parents[2]
RUNTIME_MANUAL_DIR = ROOT / "runtime" / "manual_query_runs"
DEFAULT_PLATFORMS = ("xiaohongshu", "weixin", "douyin")
QUERY_MAX_COUNT = 500
QUERY_MAX_CHARS = 500


class ManualQueryEntry(BaseModel):
    query_text: str
    axes: dict[str, Any] = Field(default_factory=dict)
    metadata: dict[str, Any] = Field(default_factory=dict)
    filter_reason: str | None = None


class ManualQueryBatchRequest(BaseModel):
    name: str | None = None
    queries: list[Any] | None = None
    families: list[dict[str, Any]] | None = None
    target_platforms: list[str] | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)
    search_limit: int = Field(default=10, ge=1, le=50)
    display_limit: int = Field(default=5, ge=1, le=50)
    decode_limit: int = Field(default=100, ge=0, le=500)


def _as_request(payload: Any) -> ManualQueryBatchRequest:
    if isinstance(payload, list):
        payload = {"queries": payload}
    if not isinstance(payload, dict):
        raise HTTPException(status_code=422, detail="请求体必须是对象或 query 数组")
    return ManualQueryBatchRequest.model_validate(payload)


def _query_text(value: Any) -> str:
    if value is None:
        return ""
    return str(value).strip()


def _entry_from_value(value: Any, *, family: dict[str, Any] | None = None) -> ManualQueryEntry | None:
    if isinstance(value, str):
        query_text = _query_text(value)
        axes: dict[str, Any] = {}
        metadata: dict[str, Any] = {}
        filter_reason = None
    elif isinstance(value, dict):
        query_text = _query_text(value.get("query_text") or value.get("query"))
        axes = value.get("axes") or value.get("parts") or {}
        if not isinstance(axes, dict):
            axes = {}
        metadata = value.get("metadata") or {}
        if not isinstance(metadata, dict):
            metadata = {}
        filter_reason = value.get("filter_reason") or value.get("reason")
    else:
        return None

    if not query_text:
        return None
    if len(query_text) > QUERY_MAX_CHARS:
        raise HTTPException(status_code=422, detail=f"query 过长，最多 {QUERY_MAX_CHARS} 字符")

    merged_metadata = dict(metadata)
    merged_metadata["family_key"] = "manual"
    if family:
        merged_metadata["source_family_key"] = family.get("key") or family.get("family_key")
        merged_metadata["source_family_name"] = family.get("name") or family.get("title")
    return ManualQueryEntry(
        query_text=query_text,
        axes=axes,
        metadata=merged_metadata,
        filter_reason=_query_text(filter_reason) or None,
    )


def _normalize_entries(request: ManualQueryBatchRequest) -> list[ManualQueryEntry]:
    entries: list[ManualQueryEntry] = []
    for value in request.queries or []:
        entry = _entry_from_value(value)
        if entry:
            entries.append(entry)

    for family in request.families or []:
        if not isinstance(family, dict):
            continue
        for value in family.get("items") or []:
            entry = _entry_from_value(value, family=family)
            if entry:
                entries.append(entry)

    deduped: list[ManualQueryEntry] = []
    seen: set[str] = set()
    for entry in entries:
        key = entry.query_text
        if key in seen:
            continue
        seen.add(key)
        deduped.append(entry)
        if len(deduped) > QUERY_MAX_COUNT:
            raise HTTPException(status_code=422, detail=f"一次最多提交 {QUERY_MAX_COUNT} 条 query")
    if not deduped:
        raise HTTPException(status_code=422, detail="没有可提交的 query")
    return deduped


def _normalize_platforms(platforms: list[str] | None) -> list[str]:
    values = platforms or list(DEFAULT_PLATFORMS)
    if not values:
        raise HTTPException(status_code=422, detail="至少选择一个平台")
    seen: set[str] = set()
    out: list[str] = []
    for platform in values:
        key = str(platform).strip()
        if key not in DEFAULT_PLATFORMS:
            raise HTTPException(status_code=422, detail=f"不支持的平台：{key}")
        if key not in seen:
            seen.add(key)
            out.append(key)
    return out


def _pipeline_command(
    *,
    batch_id: str,
    run_key: str,
    platforms: list[str],
    search_limit: int,
    display_limit: int,
    decode_limit: int,
) -> list[str]:
    cmd = [
        sys.executable,
        str(ROOT / "scripts" / "run_creation_pipeline.py"),
        "--batch-id",
        batch_id,
        "--search-limit",
        str(search_limit),
        "--display-limit",
        str(display_limit),
        "--decode-limit",
        str(decode_limit),
        "--run-key",
        run_key,
        "--env-file",
        _env_file(),
    ]
    for platform in platforms:
        cmd.extend(["--platform", platform])
    return cmd


@router.post("/manual")
def create_manual_query_batch(
    payload: Any = Body(...),
    db_config: CreationDbConfig = Depends(get_creation_db_config),
) -> dict[str, Any]:
    request = _as_request(payload)
    entries = _normalize_entries(request)
    platforms = _normalize_platforms(request.target_platforms)
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    name = request.name or f"manual-query-{timestamp}"

    batch_metadata = {
        "family_key": "manual",
        "source": "manual_api",
        "query_count": len(entries),
        "platforms": platforms,
        **request.metadata,
    }

    with transaction(db_config) as conn:
        repo = PostgresAcquisitionRepository(conn)
        batch = repo.create_query_batch(
            name=name,
            source_type="manual",
            generation_method="manual_query_api_v1",
            target_platforms=platforms,
            status="ready",
            metadata=batch_metadata,
        )
        queries = [
            repo.add_query(
                batch_id=batch.id,
                query_text=entry.query_text,
                axes=entry.axes,
                keep=True,
                filter_reason=entry.filter_reason,
                status="ready",
                sort_order=index,
                metadata=entry.metadata,
            )
            for index, entry in enumerate(entries)
        ]
        run_key = f"manual-api:{batch.id}:{timestamp}"
        log_path = RUNTIME_MANUAL_DIR / f"{run_key.replace(':', '-')}.log"
        cmd = _pipeline_command(
            batch_id=str(batch.id),
            run_key=run_key,
            platforms=platforms,
            search_limit=request.search_limit,
            display_limit=request.display_limit,
            decode_limit=request.decode_limit,
        )
        run_metadata = {
            "source": "manual_api",
            "batch_id": str(batch.id),
            "platforms": platforms,
            "search_limit": request.search_limit,
            "display_limit": request.display_limit,
            "decode_limit": request.decode_limit,
            "dry_ingest_record": True,
            "log_path": str(log_path),
            "command": cmd,
        }
        run = repo.create_acquisition_run(
            batch_id=batch.id,
            run_key=run_key,
            status="pending",
            note="manual query API queued",
            metadata=run_metadata,
        )

    RUNTIME_MANUAL_DIR.mkdir(parents=True, exist_ok=True)
    env = os.environ.copy()
    env["PYTHONPATH"] = f"{ROOT}:{env.get('PYTHONPATH', '')}".rstrip(":")
    try:
        with log_path.open("ab") as stream:
            process = subprocess.Popen(
                cmd,
                cwd=str(ROOT),
                env=env,
                stdout=stream,
                stderr=subprocess.STDOUT,
                start_new_session=True,
            )
    except OSError as exc:
        with transaction(db_config) as conn:
            PostgresAcquisitionRepository(conn).update_acquisition_run(
                run.id,
                status="failed",
                error_message=f"启动手动 query pipeline 失败：{exc}",
                metadata={"log_path": str(log_path), "command": cmd},
        )
        raise HTTPException(status_code=500, detail="启动后台 pipeline 失败") from exc

    return {
        "status": "queued",
        "batch_id": str(batch.id),
        "run_id": str(run.id),
        "run_key": run_key,
        "pid": process.pid,
        "query_count": len(queries),
        "queries": [_model_dump(query) for query in queries],
        "summary_url": f"/api/acquisition/runs/{run.id}/summary",
        "log_path": str(log_path),
    }
