"""API routes for the Kerf Engine trace and simplify endpoints."""

import json
import time

from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import Response

from output import generate_dxf, generate_json, generate_svg
from pipeline.postprocess import postprocess_svg
from pipeline.preprocessing import preprocess
from pipeline.vectorize import potrace_trace, vtracer_trace
from presets.loader import all_presets, preset_names, resolve_params

router = APIRouter()


@router.get("/engine/health")
async def health():
    """Healthcheck endpoint for container orchestration."""
    return {"status": "ok"}


VALID_MODES = {"potrace", "vtracer"}
VALID_OUTPUT_FORMATS = {"svg", "dxf", "json"}


def _format_response(
    result,
    output_format: str,
    warnings: list[str],
    processing_ms: float,
    *,
    units: str | None = None,
    scale_factor: float = 1.0,
):
    """Build a standardized response from a PostProcessResult and output format.

    SVG and JSON return a JSON envelope with output + metadata.
    DXF returns raw bytes with application/dxf content type.

    For DXF output, *units* and *scale_factor* are forwarded to
    :func:`generate_dxf` so the resulting file contains correct unit
    headers and real-world coordinate values.
    """
    metadata = {
        "format": output_format,
        "path_count": len(result.paths),
        "node_count_total": result.total_nodes,
        "open_paths": result.open_path_count,
        "island_count": result.island_count,
        "warnings": warnings,
        "processing_ms": processing_ms,
    }

    if output_format == "dxf":
        dxf_kwargs: dict = {}
        if units is not None:
            dxf_kwargs["units"] = units
        if scale_factor != 1.0:
            dxf_kwargs["scale_factor"] = scale_factor
        dxf_bytes = generate_dxf(result, **dxf_kwargs)
        return Response(
            content=dxf_bytes,
            media_type="application/dxf",
            headers={
                "Content-Disposition": "attachment; filename=output.dxf",
                "X-Kerf-Metadata": json.dumps(metadata),
            },
        )
    elif output_format == "json":
        return {
            "output": json.loads(generate_json(result)),
            "format": "json",
            "metadata": metadata,
        }
    else:
        return {
            "output": generate_svg(result),
            "format": "svg",
            "metadata": metadata,
        }


@router.get("/engine/presets")
async def list_presets():
    """Return all available presets and their parameter values."""
    return {"presets": all_presets()}


@router.post("/engine/trace")
async def trace(
    file: UploadFile = File(...),
    mode: str = Form(None),
    output_format: str = Form("svg"),
    preset: str = Form("sign"),
    params: str = Form("{}"),
):
    """Convert a raster image to vector via the preprocessing + vectorization + post-processing pipeline.

    Supports three output formats: svg (default), dxf, json.
    A preset provides default parameters for each pipeline stage.
    User params override preset defaults.
    """
    if output_format not in VALID_OUTPUT_FORMATS:
        raise HTTPException(
            status_code=422,
            detail=f"Unsupported output_format '{output_format}'. Must be one of: {', '.join(sorted(VALID_OUTPUT_FORMATS))}",
        )

    try:
        user_params = json.loads(params)
    except json.JSONDecodeError as exc:
        raise HTTPException(status_code=422, detail=f"Invalid params JSON: {exc}")

    # If mode is explicitly provided, inject it into user_params for resolve_params
    if mode is not None:
        user_params.setdefault("mode", mode)

    # Validate the preset name
    valid_presets = preset_names()
    if preset not in valid_presets:
        raise HTTPException(
            status_code=422,
            detail=f"Unknown preset '{preset}'. Must be one of: {', '.join(valid_presets)}",
        )

    # Resolve effective parameters: preset defaults + user overrides
    resolved = resolve_params(preset, user_params)
    effective_mode = resolved["vectorization_mode"]

    if effective_mode not in VALID_MODES:
        raise HTTPException(
            status_code=422,
            detail=f"Invalid mode '{effective_mode}'. Must be one of: {', '.join(sorted(VALID_MODES))}",
        )

    raw_bytes = await file.read()
    if not raw_bytes:
        raise HTTPException(status_code=422, detail="Uploaded file is empty")

    warnings: list[str] = []
    start = time.perf_counter()

    try:
        preprocessed = preprocess(raw_bytes, params=resolved["preprocessing"])
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=f"Preprocessing failed: {exc}")

    try:
        vec_params = resolved["vectorizer_params"]
        if effective_mode == "potrace":
            svg_output = potrace_trace(preprocessed, **{
                k: v for k, v in vec_params.items()
                if k in ("turdsize", "alphamax", "opticurve", "opttolerance")
            })
        else:
            svg_output = vtracer_trace(preprocessed, **{
                k: v for k, v in vec_params.items()
                if k in (
                    "colormode", "hierarchical", "filter_speckle", "color_precision",
                    "layer_difference", "corner_threshold", "length_threshold",
                    "splice_threshold", "mode", "path_precision", "max_iterations",
                )
            })
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"Vectorization failed: {exc}")

    # Post-processing: RDP simplification, island detection, open path analysis
    post = resolved["postprocessing"]
    epsilon = float(post.get("epsilon", 1.0))
    close_tolerance = float(post.get("close_tolerance", 1.0))
    auto_close = bool(post.get("auto_close", False))
    try:
        result = postprocess_svg(
            svg_output,
            epsilon=epsilon,
            close_tolerance=close_tolerance,
            auto_close=auto_close,
        )
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"Post-processing failed: {exc}")

    processing_ms = round((time.perf_counter() - start) * 1000, 2)

    return _format_response(result, output_format, warnings, processing_ms)


VALID_UNITS = {"inches", "mm"}


@router.post("/engine/simplify")
async def simplify(
    file: UploadFile = File(...),
    epsilon: float = Form(1.0),
    output_format: str = Form("svg"),
    units: str | None = Form(None),
    scale_factor: float = Form(1.0),
):
    """Simplify an existing SVG using RDP path simplification.

    Accepts an SVG file upload, applies Ramer-Douglas-Peucker simplification
    with the given epsilon, and returns the result in the requested format.

    For DXF output, *units* (``'inches'`` or ``'mm'``) and *scale_factor*
    control the unit metadata headers and coordinate scaling in the DXF file.
    """
    if output_format not in VALID_OUTPUT_FORMATS:
        raise HTTPException(
            status_code=422,
            detail=f"Unsupported output_format '{output_format}'. Must be one of: {', '.join(sorted(VALID_OUTPUT_FORMATS))}",
        )

    if units is not None and units not in VALID_UNITS:
        raise HTTPException(
            status_code=422,
            detail=f"Unsupported units '{units}'. Must be one of: {', '.join(sorted(VALID_UNITS))}",
        )

    raw_bytes = await file.read()
    if not raw_bytes:
        raise HTTPException(status_code=422, detail="Uploaded file is empty")

    try:
        svg_str = raw_bytes.decode("utf-8")
    except UnicodeDecodeError:
        raise HTTPException(status_code=422, detail="File is not valid UTF-8 text")

    if "<svg" not in svg_str.lower():
        raise HTTPException(status_code=422, detail="File does not appear to be a valid SVG")

    warnings: list[str] = []
    start = time.perf_counter()

    try:
        result = postprocess_svg(svg_str, epsilon=epsilon)
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"Simplification failed: {exc}")

    processing_ms = round((time.perf_counter() - start) * 1000, 2)

    return _format_response(
        result, output_format, warnings, processing_ms,
        units=units, scale_factor=scale_factor,
    )
