"""FastAPI application for PX4 configuration API."""

from __future__ import annotations

import asyncio
import json
import os
import tempfile
from typing import Optional

from fastapi import (
    FastAPI,
    File,
    HTTPException,
    Query,
    UploadFile,
    WebSocket,
    WebSocketDisconnect,
)
from fastapi.middleware.cors import CORSMiddleware
from sse_starlette.sse import EventSourceResponse

from .config import settings
from .models import CalibrationRequest, Px4HealthResponse
from .services.calibration_service import calibrate_sensors
from .services.param_service import upload_parameters
from .services.sdcard_service import upload_to_sdcard
from .services.shell_service import ShellConnection
from .services.health_service import read_px4_health

app = FastAPI(
    title=settings.api_title,
    version=settings.api_version,
    description=settings.api_description,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

active_shell_connections: dict[str, ShellConnection] = {}


@app.get("/api/px4/health", response_model=Px4HealthResponse)
async def get_px4_health(
    port: Optional[str] = Query(None),
    baudrate: Optional[int] = Query(None),
):
    """Return a PX4 health snapshot (one-shot JSON)."""

    port = port or settings.default_port
    baudrate = baudrate or settings.default_baudrate

    return await read_px4_health(port=port, baudrate=baudrate)

@app.post("/api/calibration/start")
async def start_calibration(request: CalibrationRequest):
    port = request.port or settings.default_port
    baudrate = request.baudrate or settings.default_baudrate

    async def event_generator():
        try:
            async for payload in calibrate_sensors(
                port=port,
                baudrate=baudrate,
                calibration_type=request.calibration_type,  # <-- enum here
            ):
                # Convert dict -> SSE frame
                yield f"data: {json.dumps(payload)}\n\n"
        except Exception as exc:
            yield f"data: {json.dumps({'type': 'error', 'message': str(exc)})}\n\n"
        finally:
            yield f"data: {json.dumps({'type': 'complete'})}\n\n"

    return EventSourceResponse(event_generator())


@app.post("/api/params/upload")
async def upload_params(
    file: UploadFile = File(...),
    port: Optional[str] = Query(None, description="Serial port (default: /dev/pixhawk)"),
    baudrate: Optional[int] = Query(None, description="Serial baudrate (default: 2000000)"),
):
    """Upload parameters from a `.params` file and stream progress."""

    port = port or settings.default_port
    baudrate = baudrate or settings.default_baudrate

    with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".params") as tmp_file:
        content = await file.read()
        tmp_file.write(content.decode("utf-8"))
        tmp_file_path = tmp_file.name

    async def event_generator():
        try:
            async for message in upload_parameters(
                param_file_path=tmp_file_path,
                port=port,
                baudrate=baudrate,
            ):
                yield message
        except Exception as exc:  # pragma: no cover
            yield f'data: {json.dumps({"type": "error", "message": str(exc)})}\n\n'
        finally:
            yield f'data: {json.dumps({"type": "complete"})}\n\n'
            try:
                os.unlink(tmp_file_path)
            except OSError:
                pass

    return EventSourceResponse(event_generator())


@app.post("/api/sdcard/upload")
async def upload_sdcard_file(
    file: UploadFile = File(...),
    port: Optional[str] = Query(None, description="Serial port (default: /dev/pixhawk)"),
    baudrate: Optional[int] = Query(None, description="Serial baudrate (default: 2000000)"),
):
    """Upload a file (e.g. extras.txt) to the PX4 SD card."""

    port = port or settings.default_port
    baudrate = baudrate or settings.default_baudrate

    with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
        content = await file.read()
        tmp_file.write(content.decode("utf-8"))
        tmp_file_path = tmp_file.name

    async def event_generator():
        try:
            async for message in upload_to_sdcard(
                file_path=tmp_file_path,
                port=port,
                baudrate=baudrate,
            ):
                yield message
        except Exception as exc:  # pragma: no cover
            yield f'data: {json.dumps({"type": "error", "message": str(exc)})}\n\n'
        finally:
            yield f'data: {json.dumps({"type": "complete"})}\n\n'
            try:
                os.unlink(tmp_file_path)
            except OSError:
                pass

    return EventSourceResponse(event_generator())


@app.websocket("/api/shell/connect")
async def shell_websocket(
    websocket: WebSocket,
    port: Optional[str] = Query(None, description="Serial port (default: /dev/pixhawk)"),
    baudrate: Optional[int] = Query(None, description="Serial baudrate (default: 2000000)"),
):
    """WebSocket endpoint for interactive PX4 shell access."""

    await websocket.accept()

    port = port or settings.default_port
    baudrate = baudrate or settings.default_baudrate

    connection_id = f"{port}:{baudrate}"
    shell_conn = None

    try:
        shell_conn = ShellConnection(port=port, baudrate=baudrate)
        active_shell_connections[connection_id] = shell_conn
        await shell_conn.connect()

        await websocket.send_text("Connected to PX4 shell. Type commands and press Enter.\n")

        async def send_output():
            try:
                while shell_conn.connected:
                    output = await shell_conn.receive_output()
                    if output:
                        await websocket.send_text(output)
                    await asyncio.sleep(0.05)
            except asyncio.CancelledError:  # pragma: no cover
                pass
            except Exception as exc:  # pragma: no cover
                try:
                    await websocket.send_text(f"Error receiving output: {exc}\n")
                except Exception:
                    pass

        output_task = asyncio.create_task(send_output())

        while True:
            try:
                command = await websocket.receive_text()
                if command.strip():
                    await shell_conn.send_command(command)
            except WebSocketDisconnect:
                break
            except Exception as exc:
                await websocket.send_text(f"Error: {exc}\n")

        output_task.cancel()

    except Exception as exc:  # pragma: no cover
        await websocket.send_text(f"Connection error: {exc}\n")
    finally:
        if shell_conn:
            await shell_conn.disconnect()
        if connection_id in active_shell_connections:
            del active_shell_connections[connection_id]
        try:
            await websocket.close()
        except Exception:
            pass


def run(host: str = "0.0.0.0", port: int = 8000, reload: bool = False, log_level: str = "info") -> None:
    """Run the FastAPI application using Uvicorn."""

    import uvicorn

    uvicorn.run(
        "px4_configuration.api.main:app",
        host=host,
        port=port,
        reload=reload,
        log_level=log_level,
    )


def main() -> None:
    """Console script entry point for running the API server."""

    import argparse

    parser = argparse.ArgumentParser(
        prog="px4-config-api",
        description="Run the PX4 Configuration REST API server",
    )
    parser.add_argument("--host", default="0.0.0.0", help="Bind address (default: 0.0.0.0)")
    parser.add_argument("--port", type=int, default=8000, help="Listen port (default: 8000)")
    parser.add_argument(
        "--reload",
        action="store_true",
        help="Enable auto-reload (development only)",
    )
    parser.add_argument(
        "--log-level",
        default="info",
        choices=["critical", "error", "warning", "info", "debug", "trace"],
        help="Set Uvicorn log level (default: info)",
    )

    args = parser.parse_args()
    run(host=args.host, port=args.port, reload=args.reload, log_level=args.log_level)


if __name__ == "__main__":
    main()

