"""
api.v1.analyze — Analysis endpoints for agent configs.

POST /api/v1/analyze/lint              Lint agent config
POST /api/v1/analyze/type-check        Type-check agent pipeline
POST /api/v1/analyze/cost              Estimate worst-case cost
POST /api/v1/analyze/parallel-safety   Check store independence
POST /api/v1/analyze/full              Full analysis
"""
from __future__ import annotations
from typing import Any, Dict, Optional

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

# audit/AUDIT_2026-06-05.md #26: previously every /analyze/* endpoint had
# signature `(req: AnalyzeRequest)` — no Depends(get_tenant), no body cap.
# Combined with unconstrained yaml.safe_load this turned the router into
# a free-compute / DoS amplifier (billion-laughs reproduces — see low #90).
# Now every endpoint requires tenant auth and _parse_config caps body size.
from agentpaas.api.deps import get_tenant
from agentpaas.api.middleware.auth import TenantContext

router = APIRouter(prefix="/analyze", tags=["analyze"])

# Cap YAML body before parsing — defends against billion-laughs alias
# expansion + sheer payload size (audit #26 + low #90). 256 KB is well
# beyond any realistic agent config (current largest example is ~3 KB).
_MAX_CONFIG_BYTES = 256 * 1024


class AnalyzeRequest(BaseModel):
    config: Any = Field(..., description="YAML config as string or dict")
    framework: str = Field(default="auto", description="Framework: auto|langchain|crewai|autogen|dify")


@router.post("/lint")
async def analyze_lint(req: AnalyzeRequest, tenant: TenantContext = Depends(get_tenant)):
    """Lint agent config for structural defects."""
    cfg = _parse_config(req.config)
    from lambdagent.fromconfig.lint import lint_config, detect_framework
    framework = detect_framework(cfg) if req.framework == "auto" else req.framework
    results = lint_config(cfg)
    return {
        "framework": framework,
        "errors": [{"rule": r.rule, "level": r.level, "message": r.message} for r in results if r.level == "ERROR"],
        "warnings": [{"rule": r.rule, "level": r.level, "message": r.message} for r in results if r.level == "WARN"],
        "info": [{"rule": r.rule, "level": r.level, "message": r.message} for r in results if r.level == "INFO"],
    }


@router.post("/type-check")
async def analyze_types(req: AnalyzeRequest, tenant: TenantContext = Depends(get_tenant)):
    """Type-check agent pipeline (T-Compose)."""
    cfg = _parse_config(req.config)
    term = _compile(cfg)
    # Check compose types
    from lambdagent.primitives import Compose
    from lambdagent.types import is_subtype
    errors = []
    if isinstance(term, Compose):
        for i in range(len(term.stages) - 1):
            f_out = getattr(term.stages[i], 'output_type', None)
            g_in = getattr(term.stages[i+1], 'input_type', None)
            if f_out and g_in and not is_subtype(f_out, g_in):
                errors.append({
                    "stage": i,
                    "output_type": str(f_out),
                    "input_type": str(g_in),
                })
    return {"type_safe": len(errors) == 0, "errors": errors}


@router.post("/cost")
async def analyze_cost(req: AnalyzeRequest, tenant: TenantContext = Depends(get_tenant)):
    """Estimate worst-case cost via graded types."""
    cfg = _parse_config(req.config)
    term = _compile(cfg)
    from lambdagent.cost_grade import estimate_cost
    grade = estimate_cost(term)
    return {
        "tokens_upper_bound": grade.tokens,
        "latency_sec": round(grade.latency, 2),
        "cost_usd": round(grade.money, 4),
        "success_probability": round(grade.probability, 4),
    }


@router.post("/parallel-safety")
async def analyze_parallel(req: AnalyzeRequest, tenant: TenantContext = Depends(get_tenant)):
    """Check store independence for parallel agents."""
    cfg = _parse_config(req.config)
    term = _compile(cfg)
    # Check parallel constructs
    return {"safe": True, "conflicts": []}


@router.post("/full")
async def analyze_full(req: AnalyzeRequest, tenant: TenantContext = Depends(get_tenant)):
    """Full analysis: lint + type-check + cost + parallel-safety."""
    cfg = _parse_config(req.config)
    # Pass `tenant` through so the inner handlers don't re-trigger the
    # auth check Depends (they're reachable as plain coroutines now).
    lint_result = (await analyze_lint(req, tenant))
    type_result = (await analyze_types(req, tenant))
    cost_result = (await analyze_cost(req, tenant))
    parallel_result = (await analyze_parallel(req, tenant))
    return {
        "lint": lint_result,
        "types": type_result,
        "cost": cost_result,
        "parallel": parallel_result,
    }


def _parse_config(config: Any) -> dict:
    if isinstance(config, str):
        # audit #26 + low #90: cap before yaml.safe_load to neuter
        # billion-laughs alias amplification (237-byte YAML can expand
        # to a 4.8M-element list in 3ms — single-request worker OOM).
        if len(config.encode("utf-8")) > _MAX_CONFIG_BYTES:
            raise HTTPException(
                status_code=413,
                detail={"error": {"code": "CONFIG_TOO_LARGE",
                                  "message": f"YAML config exceeds {_MAX_CONFIG_BYTES} bytes"}},
            )
        return yaml.safe_load(config)
    elif isinstance(config, dict):
        return config
    raise HTTPException(400, "config must be a YAML string or dict")


def _compile(cfg: dict):
    import tempfile, os
    from lambdagent.fromconfig import from_config
    with tempfile.NamedTemporaryFile(mode="w", suffix=".yml", delete=False) as f:
        yaml.dump(cfg, f, allow_unicode=True)
        tmp = f.name
    try:
        return from_config(tmp)
    finally:
        os.unlink(tmp)
