//! REST HTTP API handlers for PlasmaVMC
//!
//! Implements REST endpoints as specified in T050.S5:
//! - GET /api/v1/vms - List VMs
//! - POST /api/v1/vms - Create VM
//! - GET /api/v1/vms/{id} - Get VM details
//! - DELETE /api/v1/vms/{id} - Delete VM
//! - POST /api/v1/vms/{id}/start - Start VM
//! - POST /api/v1/vms/{id}/stop - Stop VM
//! - GET /health - Health check

use axum::{
    extract::{Path, State},
    http::StatusCode,
    http::HeaderMap,
    routing::{get, post},
    Json, Router,
};
use plasmavmc_api::proto::{
    CreateVmRequest, DeleteVmRequest, GetVmRequest, ListVmsRequest,
    StartVmRequest, StopVmRequest, MigrateVmRequest, VirtualMachine as ProtoVm,
    vm_service_server::VmService,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tonic::Request;
use tonic::Code;

use iam_service_auth::{resolve_tenant_ids_from_context, AuthService, TenantContext};
use crate::VmServiceImpl;

/// REST API state
#[derive(Clone)]
pub struct RestApiState {
    pub vm_service: Arc<VmServiceImpl>,
    pub auth_service: Arc<AuthService>,
}

/// Standard REST error response
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: ErrorDetail,
    pub meta: ResponseMeta,
}

#[derive(Debug, Serialize)]
pub struct ErrorDetail {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

#[derive(Debug, Serialize)]
pub struct ResponseMeta {
    pub request_id: String,
    pub timestamp: String,
}

impl ResponseMeta {
    fn new() -> Self {
        Self {
            request_id: uuid::Uuid::new_v4().to_string(),
            timestamp: chrono::Utc::now().to_rfc3339(),
        }
    }
}

/// Standard REST success response
#[derive(Debug, Serialize)]
pub struct SuccessResponse<T> {
    pub data: T,
    pub meta: ResponseMeta,
}

impl<T> SuccessResponse<T> {
    fn new(data: T) -> Self {
        Self {
            data,
            meta: ResponseMeta::new(),
        }
    }
}

/// VM creation request
#[derive(Debug, Deserialize)]
pub struct CreateVmRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub vcpus: Option<u32>,
    pub memory_mib: Option<u64>,
    pub hypervisor: Option<String>,
    #[serde(default)]
    pub disks: Vec<DiskSpecRest>,
}

#[derive(Debug, Deserialize)]
pub struct DiskSpecRest {
    pub id: String,
    pub source: DiskSourceRest,
    pub size_gib: Option<u64>,
    pub bus: Option<String>,
    pub cache: Option<String>,
    pub boot_index: Option<u32>,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DiskSourceRest {
    Image { image_id: String },
    Volume { volume_id: String },
    Blank,
}

/// VM migration request
#[derive(Debug, Deserialize)]
pub struct MigrateVmRequestRest {
    pub destination_node_id: String,
    pub timeout_seconds: Option<u32>,
    pub wait: Option<bool>,
}

/// VM response
#[derive(Debug, Serialize)]
pub struct VmResponse {
    pub id: String,
    pub name: String,
    pub state: String,
    pub cpus: u32,
    pub memory_mb: u64,
}

impl From<ProtoVm> for VmResponse {
    fn from(vm: ProtoVm) -> Self {
        let cpus = vm.spec.as_ref().and_then(|s| s.cpu.as_ref()).map(|c| c.vcpus).unwrap_or(1);
        let memory_mb = vm.spec.as_ref().and_then(|s| s.memory.as_ref()).map(|m| m.size_mib).unwrap_or(512);
        let state = format!("{:?}", vm.state());

        Self {
            id: vm.id,
            name: vm.name,
            state,
            cpus,
            memory_mb,
        }
    }
}

/// VMs list response
#[derive(Debug, Serialize)]
pub struct VmsResponse {
    pub vms: Vec<VmResponse>,
}

/// Build the REST API router
pub fn build_router(state: RestApiState) -> Router {
    Router::new()
        .route("/api/v1/vms", get(list_vms).post(create_vm))
        .route("/api/v1/vms/{id}", get(get_vm).delete(delete_vm))
        .route("/api/v1/vms/{id}/start", post(start_vm))
        .route("/api/v1/vms/{id}/stop", post(stop_vm))
        .route("/api/v1/vms/{id}/migrate", post(migrate_vm))
        .route("/health", get(health_check))
        .with_state(state)
}

/// Health check endpoint
async fn health_check() -> (StatusCode, Json<SuccessResponse<serde_json::Value>>) {
    (
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "status": "healthy" }))),
    )
}

/// GET /api/v1/vms - List VMs
async fn list_vms(
    State(state): State<RestApiState>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<VmsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut req = Request::new(ListVmsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        page_size: 100,
        page_token: String::new(),
        filter: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state.vm_service.list_vms(req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "LIST_FAILED", &e.message()))?;

    let vms: Vec<VmResponse> = response.into_inner().vms.into_iter().map(VmResponse::from).collect();

    Ok(Json(SuccessResponse::new(VmsResponse { vms })))
}

/// POST /api/v1/vms - Create VM
async fn create_vm(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateVmRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<VmResponse>>), (StatusCode, Json<ErrorResponse>)> {
    use plasmavmc_api::proto::{
        disk_source, CpuSpec, DiskBus, DiskCache, DiskSource, DiskSpec, HypervisorType,
        MemorySpec,
    };

    let hypervisor_type = match req.hypervisor.as_deref() {
        Some("kvm") => HypervisorType::Kvm,
        Some("firecracker") => HypervisorType::Firecracker,
        Some("mvisor") => HypervisorType::Mvisor,
        _ => HypervisorType::Unspecified,
    };

    let disks = req
        .disks
        .into_iter()
        .map(|disk| DiskSpec {
            id: disk.id,
            source: Some(DiskSource {
                source: Some(match disk.source {
                    DiskSourceRest::Image { image_id } => disk_source::Source::ImageId(image_id),
                    DiskSourceRest::Volume { volume_id } => {
                        disk_source::Source::VolumeId(volume_id)
                    }
                    DiskSourceRest::Blank => disk_source::Source::Blank(true),
                }),
            }),
            size_gib: disk.size_gib.unwrap_or(10),
            bus: match disk.bus.as_deref() {
                Some("scsi") => DiskBus::Scsi as i32,
                Some("ide") => DiskBus::Ide as i32,
                Some("sata") => DiskBus::Sata as i32,
                _ => DiskBus::Virtio as i32,
            },
            cache: match disk.cache.as_deref() {
                Some("writeback") => DiskCache::Writeback as i32,
                Some("writethrough") => DiskCache::Writethrough as i32,
                _ => DiskCache::Writeback as i32,
            },
            boot_index: disk.boot_index.unwrap_or_default(),
        })
        .collect();

    let tenant =
        resolve_rest_tenant(&state, &headers, req.org_id.as_deref(), req.project_id.as_deref())
            .await?;
    let mut grpc_req = Request::new(CreateVmRequest {
        name: req.name,
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        spec: Some(plasmavmc_api::proto::VmSpec {
            cpu: Some(CpuSpec {
                vcpus: req.vcpus.unwrap_or(1),
                cores_per_socket: 1,
                sockets: 1,
                cpu_model: String::new(),
            }),
            memory: Some(MemorySpec {
                size_mib: req.memory_mib.unwrap_or(512),
                hugepages: false,
            }),
            disks,
            network: vec![],
            boot: None,
            security: None,
        }),
        hypervisor: hypervisor_type as i32,
        metadata: Default::default(),
        labels: Default::default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state.vm_service.create_vm(grpc_req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "CREATE_FAILED", &e.message()))?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(VmResponse::from(response.into_inner()))),
    ))
}

/// GET /api/v1/vms/{id} - Get VM details
async fn get_vm(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<VmResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut req = Request::new(GetVmRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vm_id: id,
    });
    req.extensions_mut().insert(tenant);

    let response = state.vm_service.get_vm(req)
        .await
        .map_err(|e| {
            if e.code() == tonic::Code::NotFound {
                error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "VM not found")
            } else {
                error_response(StatusCode::INTERNAL_SERVER_ERROR, "GET_FAILED", &e.message())
            }
        })?;

    Ok(Json(SuccessResponse::new(VmResponse::from(response.into_inner()))))
}

/// DELETE /api/v1/vms/{id} - Delete VM
async fn delete_vm(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut req = Request::new(DeleteVmRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vm_id: id.clone(),
        force: false,
    });
    req.extensions_mut().insert(tenant);

    state.vm_service.delete_vm(req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "DELETE_FAILED", &e.message()))?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

/// POST /api/v1/vms/{id}/start - Start VM
async fn start_vm(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<serde_json::Value>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut req = Request::new(StartVmRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vm_id: id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state.vm_service.start_vm(req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "START_FAILED", &e.message()))?;

    Ok(Json(SuccessResponse::new(serde_json::json!({ "id": id, "action": "started" }))))
}

/// POST /api/v1/vms/{id}/stop - Stop VM
async fn stop_vm(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<serde_json::Value>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut req = Request::new(StopVmRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vm_id: id.clone(),
        force: false,
        timeout_seconds: 30,
    });
    req.extensions_mut().insert(tenant);

    state.vm_service.stop_vm(req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "STOP_FAILED", &e.message()))?;

    Ok(Json(SuccessResponse::new(serde_json::json!({ "id": id, "action": "stopped" }))))
}

/// POST /api/v1/vms/{id}/migrate - Migrate VM
async fn migrate_vm(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Path(id): Path<String>,
    Json(req): Json<MigrateVmRequestRest>,
) -> Result<Json<SuccessResponse<VmResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut grpc_req = Request::new(MigrateVmRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vm_id: id,
        destination_node_id: req.destination_node_id,
        timeout_seconds: req.timeout_seconds.unwrap_or(0),
        wait: req.wait.unwrap_or(false),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .vm_service
        .migrate_vm(grpc_req)
        .await
        .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "MIGRATE_FAILED", &e.message()))?;

    Ok(Json(SuccessResponse::new(VmResponse::from(response.into_inner()))))
}

/// Helper to create error response
fn error_response(
    status: StatusCode,
    code: &str,
    message: &str,
) -> (StatusCode, Json<ErrorResponse>) {
    (
        status,
        Json(ErrorResponse {
            error: ErrorDetail {
                code: code.to_string(),
                message: message.to_string(),
                details: None,
            },
            meta: ResponseMeta::new(),
        }),
    )
}

async fn resolve_rest_tenant(
    state: &RestApiState,
    headers: &HeaderMap,
    req_org_id: Option<&str>,
    req_project_id: Option<&str>,
) -> Result<TenantContext, (StatusCode, Json<ErrorResponse>)> {
    let tenant = state
        .auth_service
        .authenticate_headers(headers)
        .await
        .map_err(map_auth_status)?;
    resolve_tenant_ids_from_context(
        &tenant,
        req_org_id.unwrap_or(""),
        req_project_id.unwrap_or(""),
    )
    .map_err(map_auth_status)?;

    Ok(tenant)
}

fn map_auth_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
    let status_code = match status.code() {
        Code::Unauthenticated => StatusCode::UNAUTHORIZED,
        Code::PermissionDenied => StatusCode::FORBIDDEN,
        Code::InvalidArgument => StatusCode::BAD_REQUEST,
        Code::NotFound => StatusCode::NOT_FOUND,
        _ => StatusCode::INTERNAL_SERVER_ERROR,
    };
    let code = match status.code() {
        Code::Unauthenticated => "UNAUTHENTICATED",
        Code::PermissionDenied => "FORBIDDEN",
        Code::InvalidArgument => "INVALID_ARGUMENT",
        Code::NotFound => "NOT_FOUND",
        _ => "INTERNAL",
    };

    error_response(status_code, code, status.message())
}
