from __future__ import annotations

from typing import Any, Literal

from fastapi import FastAPI
from pydantic import BaseModel, Field

from .cpu_runtime import SpatialCpuRuntime, SpatialOutcome


class Resource(BaseModel):
    resource_id: str | None = None
    storage_url: str | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)


class PointCloudRequest(BaseModel):
    job_id: str | None = None
    resource: Resource
    scenes: list[str] = Field(default_factory=list)
    model_group: str | None = None
    model_version: str | None = None
    parameters: dict[str, Any] = Field(default_factory=dict)
    deployment_id: str | None = None


class PointCloudTestRequest(BaseModel):
    model_group: str
    model_version: str
    resource: Resource
    scenes: list[str] = Field(default_factory=list)
    parameters: dict[str, Any] = Field(default_factory=dict)
    deployment_id: str | None = None


class PointCloudResult(BaseModel):
    scene: str
    category: str
    confidence: float
    geometry: dict[str, Any]
    measurements: dict[str, Any]
    attributes: dict[str, Any]
    model_name: str = "pointcloud-analyzer"
    model_version: str = "v1.0.0"


class PointCloudResponse(BaseModel):
    job_id: str | None = None
    model: dict[str, str]
    results: list[PointCloudResult]


class PointCloudTestResponse(BaseModel):
    model: dict[str, str]
    parameter_snapshot: dict[str, Any]
    results: list[PointCloudResult]


class HealthResponse(BaseModel):
    status: Literal["ok"]


app = FastAPI(title="Rail UAV PointCloud Analysis Service", version="0.1.0")
runtime = SpatialCpuRuntime()


@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
    return HealthResponse(status="ok")


@app.get("/api/v1/runtime")
def runtime_status() -> dict[str, Any]:
    return runtime.status()


@app.post("/api/v1/runtime/unload")
def unload_models(model_group: str | None = None) -> dict[str, Any]:
    return {"unloaded": runtime.unload(model_group), "runtime": runtime.status()}


@app.post("/api/v1/runtime/load")
def load_model(model_group: str, model_version: str | None = None) -> dict[str, Any]:
    return {"model": runtime.load(model_group, model_version), "runtime": runtime.status()}


@app.post("/api/v1/pointcloud/analyze", response_model=PointCloudResponse)
def analyze(request: PointCloudRequest) -> PointCloudResponse:
    metadata = request.resource.metadata
    lon = float(metadata.get("longitude", 116.102))
    lat = float(metadata.get("latitude", 39.102))
    results: list[PointCloudResult] = []
    model_group = request.model_group or "pointcloud-analyzer"
    model_version = request.model_version or "v1.0.0"
    for scene in request.scenes:
        outcome = (
            runtime.analyze_change(request.resource.storage_url, metadata, request.parameters, model_version)
            if model_group == "change-detector"
            else runtime.analyze_pointcloud(request.resource.storage_url, metadata, request.parameters, model_version)
        )
        if outcome.execution_mode != "baseline":
            results.extend(_runtime_results(outcome, scene, model_group, model_version))
            continue
        if runtime.settings.fallback_mode != "baseline":
            continue
        if model_group == "change-detector":
            results.append(
                PointCloudResult(
                    scene=scene,
                    category="变化区域",
                    confidence=0.87,
                    geometry={"type": "Point", "coordinates": [lon, lat]},
                    measurements={"change_area_m2": float(metadata.get("change_area_m2", 32.4))},
                    attributes={
                        "analysis": "多时相影像变化检测基线",
                        "execution_mode": "baseline",
                        "fallback_reason": outcome.reason,
                        "configured_model": outcome.model,
                    },
                    model_name=model_group,
                    model_version=model_version,
                )
            )
            continue
        if scene == "防洪点精密监测":
            results.append(
                PointCloudResult(
                    scene=scene,
                    category="体积变化",
                    confidence=0.93,
                    geometry={"type": "Point", "coordinates": [lon, lat]},
                    measurements={
                        "volume_change_m3": float(metadata.get("volume_change_m3", 1.8)),
                        "max_height_diff_m": float(metadata.get("max_height_diff_m", 0.32)),
                        "registration_error_m": 0.06,
                    },
                    attributes={
                        "analysis": "DEM/TIF差分基线",
                        "execution_mode": "baseline",
                        "fallback_reason": outcome.reason,
                        "configured_model": outcome.model,
                    },
                )
            )
        if scene == "危树侵限":
            results.append(
                PointCloudResult(
                    scene=scene,
                    category="危树",
                    confidence=0.82,
                    geometry={"type": "Point", "coordinates": [lon, lat]},
                    measurements={"distance_to_contact_network_m": 3.5, "tree_height_m": 12.2},
                    attributes={
                        "analysis": "点云距离测量基线",
                        "execution_mode": "baseline",
                        "fallback_reason": outcome.reason,
                        "configured_model": outcome.model,
                    },
                )
            )
    return PointCloudResponse(
        job_id=request.job_id,
        model={"name": model_group, "version": model_version},
        results=_with_deployment(results, request.deployment_id),
    )


@app.post("/api/v1/pointcloud/test", response_model=PointCloudTestResponse)
def test_analysis(request: PointCloudTestRequest) -> PointCloudTestResponse:
    metadata = request.resource.metadata
    lon = float(metadata.get("longitude", 116.102))
    lat = float(metadata.get("latitude", 39.102))
    scene = request.scenes[0] if request.scenes else (
        "防洪点精密监测" if request.model_group == "pointcloud-analyzer" else "彩钢板清理"
    )
    version_bonus = 0.015 if request.model_version != "v1.0.0" else 0.0
    outcome = (
        runtime.analyze_change(request.resource.storage_url, metadata, request.parameters, request.model_version)
        if request.model_group == "change-detector"
        else runtime.analyze_pointcloud(request.resource.storage_url, metadata, request.parameters, request.model_version)
    )
    if outcome.execution_mode != "baseline":
        return PointCloudTestResponse(
            model={"name": request.model_group, "version": request.model_version},
            parameter_snapshot=request.parameters,
            results=_with_deployment(
                _runtime_results(outcome, scene, request.model_group, request.model_version),
                request.deployment_id,
            ),
        )
    if runtime.settings.fallback_mode != "baseline":
        return PointCloudTestResponse(
            model={"name": request.model_group, "version": request.model_version},
            parameter_snapshot=request.parameters,
            results=[],
        )
    if request.model_group == "change-detector":
        threshold = float(request.parameters.get("change_threshold", 0.25))
        confidence = min(0.99, max(0.05, 0.89 + version_bonus + (0.25 - threshold) * 0.08))
        results = [
            PointCloudResult(
                scene=scene,
                category="变化区域",
                confidence=round(confidence, 4),
                geometry={
                    "type": "Polygon",
                    "coordinates": [[[lon, lat], [lon + 0.0005, lat], [lon + 0.0005, lat + 0.0004], [lon, lat + 0.0004], [lon, lat]]],
                },
                measurements={
                    "change_area_m2": float(metadata.get("change_area_m2", 32.4)),
                    "registration_precision_m": float(request.parameters.get("registration_precision_m", 0.2)),
                    "change_score": round(confidence, 4),
                },
                attributes={
                    "analysis": "多时相栅格配准与差分基线",
                    "test_mode": True,
                    "execution_mode": "baseline",
                    "fallback_reason": outcome.reason,
                    "configured_model": outcome.model,
                },
                model_name=request.model_group,
                model_version=request.model_version,
            )
        ]
    else:
        voxel_size = float(request.parameters.get("voxel_size_m", 0.08))
        registration_error = max(0.018, 0.064 - version_bonus - (0.08 - voxel_size) * 0.05)
        category = "危树" if "危树" in scene else "体积变化"
        results = [
            PointCloudResult(
                scene=scene,
                category=category,
                confidence=round(0.91 + version_bonus, 4),
                geometry={"type": "Point", "coordinates": [lon, lat]},
                measurements={
                    "volume_change_m3": float(metadata.get("volume_change_m3", 1.8)),
                    "max_height_diff_m": float(metadata.get("max_height_diff_m", 0.32)),
                    "registration_error_m": round(registration_error, 4),
                    "point_count": int(metadata.get("point_count", 680000)),
                },
                attributes={
                    "analysis": "点云配准基线",
                    "test_mode": True,
                    "execution_mode": "baseline",
                    "fallback_reason": outcome.reason,
                    "configured_model": outcome.model,
                },
                model_name=request.model_group,
                model_version=request.model_version,
            )
        ]
    return PointCloudTestResponse(
        model={"name": request.model_group, "version": request.model_version},
        parameter_snapshot=request.parameters,
        results=_with_deployment(results, request.deployment_id),
    )


def _with_deployment(results: list[PointCloudResult], deployment_id: str | None) -> list[PointCloudResult]:
    if deployment_id:
        for result in results:
            result.attributes["deployment_id"] = deployment_id
    return results


def _runtime_results(
    outcome: SpatialOutcome, scene: str, model_group: str, model_version: str
) -> list[PointCloudResult]:
    converted: list[PointCloudResult] = []
    for item in outcome.results:
        converted.append(
            PointCloudResult(
                scene=scene,
                category=str(item.get("category", "空间变化")),
                confidence=float(item.get("confidence", 0)),
                geometry=dict(item.get("geometry", {})),
                measurements=dict(item.get("measurements", {})),
                attributes={
                    **dict(item.get("attributes", {})),
                    "execution_mode": outcome.execution_mode,
                    "runtime": outcome.model.get("runtime"),
                    "latency_ms": outcome.latency_ms,
                    "configured_model": outcome.model,
                },
                model_name=model_group,
                model_version=model_version,
            )
        )
    return converted
