"""Draft Web Facade. Not mounted in create_app until Web wire acceptance."""

from contextlib import asynccontextmanager
from concurrent.futures import wait
import asyncio
import time
from fastapi import APIRouter, HTTPException, Response
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, SecretStr, StrictBool, StrictInt, StrictStr

from app.application.streams import StreamApplicationService
from app.contracts.cameras import CameraConfigError, CameraStart
from app.application.camera_commands import CameraCommands, CommandUnavailable
from app.stream_config_store import ConfigPersistenceError


class ScenarioSelection(BaseModel):
    model_config = ConfigDict(extra="forbid")
    scenario_code: StrictStr = Field(alias="scenarioCode")
    enabled: StrictBool


class CameraIdentity(BaseModel):
    model_config = ConfigDict(extra="forbid")
    camera_id: StrictInt | StrictStr = Field(alias="cameraId")


class StartRequest(CameraIdentity):
    rtsp_url: SecretStr = Field(alias="rtspUrl", repr=False)
    rec_camera_id: StrictInt | StrictStr | None = Field(default=None, validation_alias=AliasChoices("recCameraId", "rec_camera_id"))
    config_version: StrictInt | StrictStr | None = Field(default=None, alias="configVersion")
    scenarios: list[ScenarioSelection] | None = None
    start_wall_time_ms: StrictInt | None = Field(default=None, alias="startWallTimeMs", ge=0)
    rtp_clock_rate: StrictInt | None = Field(default=None, alias="rtpClockRate", gt=0)


class CameraResponse(BaseModel):
    cameraId: StrictInt | StrictStr
    streamId: str | None
    runId: str | None
    runGeneration: int | None
    desiredState: str
    observedState: str | None
    status: str
    result: str
    streamUrl: str | None = None
    mediaStatus: str = "NOT_READY"
    lastError: dict | None = None
    operationId: str | None = None
    effectiveScenarios: list[dict] = Field(default_factory=list)
    skippedScenarios: list[dict] = Field(default_factory=list)


def _response(camera_id, group, action="start"):
    state = group.lifecycle.snapshot()
    observed = state["observed_state"].upper()
    return CameraResponse(
        cameraId=camera_id, streamId=group.stream_id,
        runId=state["run_id"], runGeneration=state["run_generation"],
        desiredState=state["desired_state"].upper(), observedState=observed,
        status=observed, result="FAIL" if observed == "ERROR" or (action == "start" and observed == "STOPPED") else (
            "SUCCESS" if observed == ("RUNNING" if action == "start" else "STOPPED") else "ACCEPTED"),
        lastError=state["last_error"],
    )


def build_web_router(service: StreamApplicationService, commands=None, response_budget=2.0):
    if not 0 <= response_budget < 2.5:
        raise ValueError("Response budget must leave transport headroom.")
    owner = commands if commands is not None else CameraCommands(service)

    @asynccontextmanager
    async def lifespan(_app):
        try:
            yield
        finally:
            if not await asyncio.to_thread(owner.close):
                raise RuntimeError("Camera control cleanup is pending.")

    router = APIRouter(lifespan=lifespan)

    def reply(job, response, effective=(), skipped=()):
        deadline = time.monotonic() + response_budget
        try:
            done, _ = wait((job.future,), timeout=max(0, deadline - time.monotonic()))
            if done:
                group = job.future.result()
                result = _response(job.camera_id, group, job.action)
                while result.result == "ACCEPTED" and time.monotonic() < deadline:
                    time.sleep(min(0.01, max(0, deadline - time.monotonic())))
                    result = _response(job.camera_id, group, job.action)
            else:
                result = CameraResponse(cameraId=job.camera_id, streamId=None, runId=None,
                                        runGeneration=None, desiredState="RUNNING" if job.action == "start" else "STOPPED",
                                        observedState=None, status="STARTING" if job.action == "start" else "STOPPING",
                                        result="ACCEPTED")
        except (CameraConfigError, ConfigPersistenceError, KeyError):
            raise
        except Exception:
            raise HTTPException(503, "Camera operation failed.") from None
        result.operationId = job.operation_id
        result.effectiveScenarios = list(effective) if result.result == "SUCCESS" else []
        result.skippedScenarios = list(skipped)
        response.status_code = 202 if result.result == "ACCEPTED" else (503 if result.result == "FAIL" else 200)
        return result

    @router.post("/api/v1/cameras/start", status_code=202, response_model=CameraResponse)
    def start_camera(payload: StartRequest, response: Response):
        try:
            request = CameraStart(
                camera_id=payload.camera_id, rtsp_url=payload.rtsp_url.get_secret_value(),
                rec_camera_id=payload.rec_camera_id, config_version=payload.config_version,
                scenarios=None if payload.scenarios is None else tuple(
                    (item.scenario_code, item.enabled) for item in payload.scenarios
                ),
                start_wall_time_ms=payload.start_wall_time_ms, rtp_clock_rate=payload.rtp_clock_rate,
            )
            effective, skipped = service.scenario_selection(request)
            return reply(owner.submit(payload.camera_id, request), response, effective, skipped)
        except CommandUnavailable:
            raise HTTPException(503, "Camera control is unavailable.") from None
        except CameraConfigError:
            raise HTTPException(422, "Invalid or unsupported camera configuration.") from None
        except ValueError:
            raise HTTPException(409, "Camera configuration conflicts.") from None

    @router.post("/api/v1/cameras/stop", response_model=CameraResponse)
    def stop_camera(payload: CameraIdentity, response: Response):
        try:
            return reply(owner.submit(payload.camera_id, action="stop"), response)
        except CommandUnavailable:
            raise HTTPException(503, "Camera control is unavailable.") from None
        except KeyError:
            raise HTTPException(404, "Camera is not registered.") from None
        except CameraConfigError:
            raise HTTPException(422, "Invalid camera identity.") from None

    return router
