use axum::{
    Json,
    body::Bytes,
    extract::{Extension, Path, Query, State},
    http::{HeaderMap, StatusCode, header},
    response::IntoResponse,
};
use serde::Deserialize;
use serde_json::{Value, json};

use crate::{
    error::ApiError,
    request_context::RequestContext,
    service::{
        ExportQuery, GenerateDraftPayload, ImportQuery, NewVersionPayload, OperationPayload,
        PublishPayload, TestRunPayload, UpdateOperationPayload,
    },
    state::AppState,
};

#[derive(Deserialize)]
pub struct WorkspacePath {
    pub workspace_id: String,
}

#[derive(Deserialize)]
pub struct WorkspaceOperationPath {
    pub workspace_id: String,
    pub operation_id: String,
}

#[derive(Deserialize)]
pub struct WorkspaceOperationVersionPath {
    pub workspace_id: String,
    pub operation_id: String,
    pub version: u32,
}

pub async fn list_operations(
    Path(path): Path<WorkspacePath>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let items = state
        .service
        .list_operations(&path.workspace_id.as_str().into())
        .await?;
    let total = items.len();
    Ok(Json(json!({
        "items": items,
        "page": 1,
        "page_size": total,
        "total": total
    })))
}

pub async fn create_operation(
    Path(path): Path<WorkspacePath>,
    State(state): State<AppState>,
    Json(payload): Json<OperationPayload>,
) -> Result<Json<Value>, ApiError> {
    let created = state
        .service
        .create_operation(&path.workspace_id.as_str().into(), payload)
        .await?;
    Ok(Json(json!(created)))
}

pub async fn get_operation(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let operation = state
        .service
        .get_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
        )
        .await?;
    Ok(Json(json!(operation)))
}

pub async fn update_operation(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<UpdateOperationPayload>,
) -> Result<Json<Value>, ApiError> {
    let result = state
        .service
        .update_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            payload,
        )
        .await?;
    Ok(Json(json!(result)))
}

pub async fn delete_operation(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let result = state
        .service
        .delete_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
        )
        .await?;
    Ok(Json(json!(result)))
}

pub async fn get_operation_version(
    Path(path): Path<WorkspaceOperationVersionPath>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let version = state
        .service
        .get_operation_version(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            path.version,
        )
        .await?;
    Ok(Json(json!(version)))
}

pub async fn create_version(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<NewVersionPayload>,
) -> Result<Json<Value>, ApiError> {
    let created = state
        .service
        .create_version(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            payload,
        )
        .await?;
    Ok(Json(json!(created)))
}

pub async fn publish_operation(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<PublishPayload>,
) -> Result<Json<Value>, ApiError> {
    let published = state
        .service
        .publish_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            payload.version,
        )
        .await?;
    Ok(Json(json!(published)))
}

pub async fn archive_operation(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let archived = state
        .service
        .archive_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
        )
        .await?;
    Ok(Json(json!(archived)))
}

pub async fn run_test(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Extension(request_context): Extension<RequestContext>,
    Json(payload): Json<TestRunPayload>,
) -> Result<Json<Value>, ApiError> {
    let result = state
        .service
        .run_test(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            payload,
            &request_context.request_id,
        )
        .await?;
    Ok(Json(json!(result)))
}

pub async fn upload_input_json(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<Value>,
) -> Result<Json<Value>, ApiError> {
    let sample = state
        .service
        .save_json_sample(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            crank_registry::SampleKind::InputJson,
            &payload,
        )
        .await?;

    Ok(Json(json!({
        "sample_id": sample.id.as_str(),
        "sample_kind": sample.sample_kind,
        "version": sample.version
    })))
}

pub async fn upload_output_json(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<Value>,
) -> Result<Json<Value>, ApiError> {
    let sample = state
        .service
        .save_json_sample(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            crank_registry::SampleKind::OutputJson,
            &payload,
        )
        .await?;

    Ok(Json(json!({
        "sample_id": sample.id.as_str(),
        "sample_kind": sample.sample_kind,
        "version": sample.version
    })))
}

pub async fn upload_proto_descriptor(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<Json<Value>, ApiError> {
    let source_name = header_file_name(&headers);
    let descriptor = state
        .service
        .upload_proto_file(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            source_name.as_deref(),
            body.as_ref(),
        )
        .await?;

    Ok(Json(json!(descriptor)))
}

pub async fn upload_descriptor_set(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<Json<Value>, ApiError> {
    let source_name = header_file_name(&headers);
    let descriptor = state
        .service
        .upload_descriptor_set(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            source_name.as_deref(),
            body.as_ref(),
        )
        .await?;

    Ok(Json(json!(descriptor)))
}

pub async fn upload_wsdl_descriptor(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<Json<Value>, ApiError> {
    let source_name = header_file_name(&headers);
    let descriptor = state
        .service
        .upload_wsdl_file(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            source_name.as_deref(),
            body.as_ref(),
        )
        .await?;

    Ok(Json(json!(descriptor)))
}

pub async fn upload_xsd_descriptor(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    headers: HeaderMap,
    body: Bytes,
) -> Result<Json<Value>, ApiError> {
    let source_name = header_file_name(&headers);
    let descriptor = state
        .service
        .upload_xsd_file(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            source_name.as_deref(),
            body.as_ref(),
        )
        .await?;

    Ok(Json(json!(descriptor)))
}

pub async fn list_grpc_services(
    Path(path): Path<WorkspaceOperationPath>,
    Query(query): Query<ExportQuery>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let services = state
        .service
        .list_grpc_services(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            query.version,
        )
        .await?;
    Ok(Json(json!({ "services": services })))
}

pub async fn list_soap_services(
    Path(path): Path<WorkspaceOperationPath>,
    Query(query): Query<ExportQuery>,
    State(state): State<AppState>,
) -> Result<Json<Value>, ApiError> {
    let services = state
        .service
        .list_soap_services(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            query.version,
        )
        .await?;
    Ok(Json(json!({ "services": services })))
}

pub async fn generate_draft(
    Path(path): Path<WorkspaceOperationPath>,
    State(state): State<AppState>,
    Json(payload): Json<GenerateDraftPayload>,
) -> Result<Json<Value>, ApiError> {
    let draft = state
        .service
        .generate_draft(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            payload,
        )
        .await?;
    Ok(Json(json!(draft)))
}

pub async fn export_operation(
    Path(path): Path<WorkspaceOperationPath>,
    Query(query): Query<ExportQuery>,
    State(state): State<AppState>,
) -> Result<impl IntoResponse, ApiError> {
    let yaml = state
        .service
        .export_operation(
            &path.workspace_id.as_str().into(),
            &path.operation_id.as_str().into(),
            query,
        )
        .await?;
    let mut headers = HeaderMap::new();
    headers.insert(
        header::CONTENT_TYPE,
        header::HeaderValue::from_static("application/yaml"),
    );

    Ok((StatusCode::OK, headers, yaml))
}

pub async fn import_operation(
    Path(path): Path<WorkspacePath>,
    Query(query): Query<ImportQuery>,
    State(state): State<AppState>,
    body: String,
) -> Result<Json<Value>, ApiError> {
    let imported = state
        .service
        .import_operation(&path.workspace_id.as_str().into(), query, &body)
        .await?;
    Ok(Json(json!(imported)))
}

fn header_file_name(headers: &HeaderMap) -> Option<String> {
    headers
        .get("x-file-name")
        .and_then(|value| value.to_str().ok())
        .map(ToOwned::to_owned)
}
