use actix_web::{
    HttpRequest, HttpResponse, post,
    web::{self, Data, Json},
};
use serde::Deserialize;
use serde_json::{Value, json};

use crate::AppState;
use crate::api::auth::authorize_static_admin_key;
use crate::api::provision::register_provisioned_client;
use crate::api::response::{
    api_success, bad_request, conflict, internal_error, service_unavailable,
};
use crate::config::Config;
use crate::features::provisioning::core::{json_object_insert_if_missing, run_provision_sql};
use crate::features::provisioning::providers::{
    create_neon_project, create_railway_plugin, create_railway_project, create_railway_service,
    create_render_postgres_service, fetch_neon_connection_uri, fetch_railway_connection_uri,
    fetch_railway_project_base_environment_id, fetch_render_connection_uri,
};
use crate::features::provisioning::types::{
    NeonConnectionParams, NeonProjectCreateParams, NeonProjectCreateResult, ProvisioningError,
    RailwayConnectionParams, RailwayPluginCreateParams, RailwayProjectCreateParams,
    RailwayServiceCreateParams, RailwayServiceCreateResult, RenderConnectionParams,
    RenderPostgresCreateParams,
};

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct NeonProviderProvisionRequest {
    client_name: String,
    #[serde(default)]
    description: Option<String>,
    #[serde(default)]
    connection_uri: Option<String>,
    #[serde(default)]
    api_key: Option<String>,
    #[serde(default)]
    project_id: Option<String>,
    #[serde(default)]
    branch_id: Option<String>,
    #[serde(default)]
    database_name: Option<String>,
    #[serde(default)]
    role_name: Option<String>,
    #[serde(default)]
    endpoint_id: Option<String>,
    #[serde(default)]
    api_base_url: Option<String>,
    #[serde(default)]
    create_project_if_missing: bool,
    #[serde(default)]
    project_name: Option<String>,
    #[serde(default)]
    project_create_payload: Option<Value>,
    #[serde(default = "default_true")]
    provision_schema: bool,
    #[serde(default = "default_true")]
    register_runtime: bool,
    #[serde(default = "default_true")]
    register_catalog: bool,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct RailwayProviderProvisionRequest {
    client_name: String,
    #[serde(default)]
    description: Option<String>,
    #[serde(default)]
    connection_uri: Option<String>,
    #[serde(default)]
    api_key: Option<String>,
    #[serde(default)]
    project_id: Option<String>,
    #[serde(default)]
    environment_id: Option<String>,
    #[serde(default)]
    service_id: Option<String>,
    #[serde(default)]
    plugin_id: Option<String>,
    #[serde(default)]
    graphql_url: Option<String>,
    #[serde(default)]
    create_project_if_missing: bool,
    #[serde(default)]
    project_name: Option<String>,
    #[serde(default)]
    project_create_input: Option<Value>,
    #[serde(default)]
    create_service_if_missing: bool,
    #[serde(default)]
    service_name: Option<String>,
    #[serde(default)]
    service_create_input: Option<Value>,
    #[serde(default)]
    create_plugin_if_missing: bool,
    #[serde(default)]
    plugin_name: Option<String>,
    #[serde(default)]
    plugin_create_input: Option<Value>,
    #[serde(default = "default_true")]
    provision_schema: bool,
    #[serde(default = "default_true")]
    register_runtime: bool,
    #[serde(default = "default_true")]
    register_catalog: bool,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
struct RenderProviderProvisionRequest {
    client_name: String,
    #[serde(default)]
    description: Option<String>,
    #[serde(default)]
    connection_uri: Option<String>,
    #[serde(default)]
    api_key: Option<String>,
    #[serde(default)]
    service_id: Option<String>,
    #[serde(default)]
    owner_id: Option<String>,
    #[serde(default)]
    api_base_url: Option<String>,
    #[serde(default)]
    create_service_if_missing: bool,
    #[serde(default)]
    service_name: Option<String>,
    #[serde(default)]
    service_create_payload: Option<Value>,
    #[serde(default)]
    plan: Option<String>,
    #[serde(default)]
    region: Option<String>,
    #[serde(default)]
    postgres_version: Option<String>,
    #[serde(default)]
    disk_size_gb: Option<u32>,
    #[serde(default = "default_true")]
    provision_schema: bool,
    #[serde(default = "default_true")]
    register_runtime: bool,
    #[serde(default = "default_true")]
    register_catalog: bool,
}

fn default_true() -> bool {
    true
}

#[derive(Debug, Clone)]
struct ProvisioningRuntimeDefaults {
    neon_api_base_url: Option<String>,
    railway_graphql_url: Option<String>,
    render_api_base_url: Option<String>,
}

impl ProvisioningRuntimeDefaults {
    fn load() -> Self {
        match Config::load() {
            Ok(config) => Self {
                neon_api_base_url: Some(config.get_provisioning_default_neon_api_base_url()),
                railway_graphql_url: Some(config.get_provisioning_default_railway_graphql_url()),
                render_api_base_url: Some(config.get_provisioning_default_render_api_base_url()),
            },
            Err(_) => Self {
                neon_api_base_url: None,
                railway_graphql_url: None,
                render_api_base_url: None,
            },
        }
    }
}

fn map_provisioning_error(context: &str, err: ProvisioningError) -> HttpResponse {
    match err {
        ProvisioningError::InvalidInput(message) => bad_request(context, message),
        ProvisioningError::Conflict(message) => conflict(context, message),
        ProvisioningError::Unavailable(message) => service_unavailable(context, message),
        ProvisioningError::Execution(message) => internal_error(context, message),
    }
}

fn required_field(name: &str, value: Option<String>) -> Result<String, HttpResponse> {
    let value: String = value.unwrap_or_default();
    if value.trim().is_empty() {
        return Err(bad_request(
            "Missing required field",
            format!(
                "Provide '{}' or set 'connection_uri' to bypass provider API lookup.",
                name
            ),
        ));
    }
    Ok(value)
}

#[post("/admin/provision/providers/neon")]
pub async fn admin_provision_neon(
    req: HttpRequest,
    state: Data<AppState>,
    body: Json<NeonProviderProvisionRequest>,
) -> HttpResponse {
    if let Err(resp) = authorize_static_admin_key(&req) {
        return resp;
    }

    let defaults: ProvisioningRuntimeDefaults = ProvisioningRuntimeDefaults::load();
    let mut project_id: Option<String> = body.project_id.clone();
    let mut branch_id: Option<String> = body.branch_id.clone();

    if project_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && body.create_project_if_missing
    {
        let api_key = match required_field("api_key", body.api_key.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let created: NeonProjectCreateResult = match create_neon_project(NeonProjectCreateParams {
            api_key,
            project_name: body
                .project_name
                .clone()
                .or_else(|| Some(body.client_name.clone())),
            project_payload: body.project_create_payload.clone(),
            api_base_url: body
                .api_base_url
                .clone()
                .or_else(|| defaults.neon_api_base_url.clone()),
        })
        .await
        {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Failed to create Neon project", err),
        };
        project_id = Some(created.project_id);
        if branch_id.is_none() {
            branch_id = created.branch_id;
        }
    }

    let pg_uri: String = if let Some(uri) = body
        .connection_uri
        .as_ref()
        .filter(|value| !value.trim().is_empty())
    {
        uri.to_string()
    } else {
        let api_key: String = match required_field("api_key", body.api_key.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let project_id: String = match required_field("project_id", project_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };

        match fetch_neon_connection_uri(NeonConnectionParams {
            api_key,
            project_id,
            branch_id: branch_id.clone(),
            database_name: body.database_name.clone(),
            role_name: body.role_name.clone(),
            endpoint_id: body.endpoint_id.clone(),
            api_base_url: body
                .api_base_url
                .clone()
                .or_else(|| defaults.neon_api_base_url.clone()),
        })
        .await
        {
            Ok(uri) => uri,
            Err(err) => return map_provisioning_error("Failed to fetch Neon connection URI", err),
        }
    };

    let statements_executed = if body.provision_schema {
        Some(match run_provision_sql(&pg_uri).await {
            Ok(total) => total,
            Err(err) => return map_provisioning_error("Failed to provision Neon database", err),
        })
    } else {
        None
    };

    let description: Option<String> = body
        .description
        .clone()
        .or_else(|| Some("Neon database managed via Athena".to_string()));

    let (runtime_registered, catalog_registered) = match register_provisioned_client(
        state.get_ref(),
        &body.client_name,
        description,
        &pg_uri,
        json!({
            "managed_by": "provision_api",
            "provider": "neon",
            "project_id": project_id,
            "branch_id": branch_id,
            "database_name": body.database_name,
            "role_name": body.role_name,
            "endpoint_id": body.endpoint_id,
            "created_project": body.create_project_if_missing,
        }),
        body.register_runtime,
        body.register_catalog,
    )
    .await
    {
        Ok(value) => value,
        Err(resp) => return resp,
    };

    api_success(
        "Provisioned Neon database",
        json!({
            "provider": "neon",
            "client_name": body.client_name,
            "pg_uri": pg_uri,
            "pipeline": {
                "provision_schema": body.provision_schema,
                "statements_executed": statements_executed,
                "register_runtime": runtime_registered,
                "register_catalog": catalog_registered,
            }
        }),
    )
}

#[post("/admin/provision/providers/railway")]
pub async fn admin_provision_railway(
    req: HttpRequest,
    state: Data<AppState>,
    body: Json<RailwayProviderProvisionRequest>,
) -> HttpResponse {
    if let Err(resp) = authorize_static_admin_key(&req) {
        return resp;
    }

    let defaults = ProvisioningRuntimeDefaults::load();
    let api_key_for_create = body
        .api_key
        .clone()
        .filter(|value| !value.trim().is_empty());
    let mut project_id = body.project_id.clone();
    let mut environment_id = body.environment_id.clone();
    let mut service_id = body.service_id.clone();
    let mut plugin_id = body.plugin_id.clone();

    if project_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && body.create_project_if_missing
    {
        let api_key = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };

        let project_input = match json_object_insert_if_missing(
            body.project_create_input.clone(),
            "name",
            Value::String(
                body.project_name
                    .clone()
                    .unwrap_or_else(|| format!("athena-{}", body.client_name)),
            ),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway project_create_input", err),
        };

        let created = match create_railway_project(RailwayProjectCreateParams {
            api_key,
            project_input,
            graphql_url: body
                .graphql_url
                .clone()
                .or_else(|| defaults.railway_graphql_url.clone()),
        })
        .await
        {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Failed to create Railway project", err),
        };
        project_id = Some(created.project_id);
        if environment_id.is_none() {
            environment_id = created.base_environment_id;
        }
    }

    if environment_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && let (Some(api_key), Some(project)) = (api_key_for_create.clone(), project_id.clone())
    {
        environment_id = match fetch_railway_project_base_environment_id(
            &api_key,
            &project,
            body.graphql_url
                .as_deref()
                .or(defaults.railway_graphql_url.as_deref()),
        )
        .await
        {
            Ok(value) => value,
            Err(err) => {
                return map_provisioning_error("Failed to resolve Railway base environment", err);
            }
        };
    }

    if service_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && body.create_service_if_missing
    {
        let api_key: String = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let project: String = match required_field("project_id", project_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let service_input: Value = match json_object_insert_if_missing(
            body.service_create_input.clone(),
            "projectId",
            Value::String(project),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway service_create_input", err),
        };
        let service_input = match json_object_insert_if_missing(
            Some(service_input),
            "name",
            Value::String(
                body.service_name
                    .clone()
                    .unwrap_or_else(|| format!("{}-service", body.client_name)),
            ),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway service_create_input", err),
        };

        let created: RailwayServiceCreateResult =
            match create_railway_service(RailwayServiceCreateParams {
                api_key,
                service_input,
                graphql_url: body
                    .graphql_url
                    .clone()
                    .or_else(|| defaults.railway_graphql_url.clone()),
            })
            .await
            {
                Ok(value) => value,
                Err(err) => return map_provisioning_error("Failed to create Railway service", err),
            };
        service_id = Some(created.service_id);
    }

    if plugin_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && body.create_plugin_if_missing
    {
        let api_key = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let project = match required_field("project_id", project_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let environment = match required_field("environment_id", environment_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let plugin_input = match json_object_insert_if_missing(
            body.plugin_create_input.clone(),
            "projectId",
            Value::String(project),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway plugin_create_input", err),
        };
        let plugin_input = match json_object_insert_if_missing(
            Some(plugin_input),
            "environmentId",
            Value::String(environment),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway plugin_create_input", err),
        };
        let plugin_input = match json_object_insert_if_missing(
            Some(plugin_input),
            "name",
            Value::String(
                body.plugin_name
                    .clone()
                    .unwrap_or_else(|| "Postgres".to_string()),
            ),
        ) {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Invalid Railway plugin_create_input", err),
        };

        let created = match create_railway_plugin(RailwayPluginCreateParams {
            api_key,
            plugin_input,
            graphql_url: body
                .graphql_url
                .clone()
                .or_else(|| defaults.railway_graphql_url.clone()),
        })
        .await
        {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Failed to create Railway plugin", err),
        };
        plugin_id = Some(created.plugin_id);
    }

    let pg_uri = if let Some(uri) = body
        .connection_uri
        .as_ref()
        .filter(|value| !value.trim().is_empty())
    {
        uri.to_string()
    } else {
        let api_key = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let project_id = match required_field("project_id", project_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let environment_id = match required_field("environment_id", environment_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };

        match fetch_railway_connection_uri(RailwayConnectionParams {
            api_key,
            project_id,
            environment_id,
            service_id: service_id.clone(),
            plugin_id: plugin_id.clone(),
            graphql_url: body
                .graphql_url
                .clone()
                .or_else(|| defaults.railway_graphql_url.clone()),
        })
        .await
        {
            Ok(uri) => uri,
            Err(err) => {
                return map_provisioning_error("Failed to fetch Railway connection URI", err);
            }
        }
    };

    let statements_executed = if body.provision_schema {
        Some(match run_provision_sql(&pg_uri).await {
            Ok(total) => total,
            Err(err) => return map_provisioning_error("Failed to provision Railway database", err),
        })
    } else {
        None
    };

    let description = body
        .description
        .clone()
        .or_else(|| Some("Railway database managed via Athena".to_string()));

    let (runtime_registered, catalog_registered) = match register_provisioned_client(
        state.get_ref(),
        &body.client_name,
        description,
        &pg_uri,
        json!({
            "managed_by": "provision_api",
            "provider": "railway",
            "project_id": project_id,
            "environment_id": environment_id,
            "service_id": service_id,
            "plugin_id": plugin_id,
            "created_project": body.create_project_if_missing,
            "created_service": body.create_service_if_missing,
            "created_plugin": body.create_plugin_if_missing,
        }),
        body.register_runtime,
        body.register_catalog,
    )
    .await
    {
        Ok(value) => value,
        Err(resp) => return resp,
    };

    api_success(
        "Provisioned Railway database",
        json!({
            "provider": "railway",
            "client_name": body.client_name,
            "pg_uri": pg_uri,
            "pipeline": {
                "provision_schema": body.provision_schema,
                "statements_executed": statements_executed,
                "register_runtime": runtime_registered,
                "register_catalog": catalog_registered,
            }
        }),
    )
}

#[post("/admin/provision/providers/render")]
pub async fn admin_provision_render(
    req: HttpRequest,
    state: Data<AppState>,
    body: Json<RenderProviderProvisionRequest>,
) -> HttpResponse {
    if let Err(resp) = authorize_static_admin_key(&req) {
        return resp;
    }

    let defaults = ProvisioningRuntimeDefaults::load();
    let api_key_for_create = body
        .api_key
        .clone()
        .filter(|value| !value.trim().is_empty());
    let mut service_id = body.service_id.clone();

    if service_id
        .as_ref()
        .is_none_or(|value| value.trim().is_empty())
        && body.create_service_if_missing
    {
        let api_key = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let created = match create_render_postgres_service(RenderPostgresCreateParams {
            api_key,
            owner_id: body.owner_id.clone(),
            service_name: body.service_name.clone(),
            service_payload: body.service_create_payload.clone(),
            plan: body.plan.clone(),
            region: body.region.clone(),
            postgres_version: body.postgres_version.clone(),
            disk_size_gb: body.disk_size_gb,
            api_base_url: body
                .api_base_url
                .clone()
                .or_else(|| defaults.render_api_base_url.clone()),
        })
        .await
        {
            Ok(value) => value,
            Err(err) => return map_provisioning_error("Failed to create Render service", err),
        };
        service_id = Some(created.service_id);
    }

    let pg_uri = if let Some(uri) = body
        .connection_uri
        .as_ref()
        .filter(|value| !value.trim().is_empty())
    {
        uri.to_string()
    } else {
        let api_key = match required_field("api_key", api_key_for_create.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        let service_id = match required_field("service_id", service_id.clone()) {
            Ok(value) => value,
            Err(resp) => return resp,
        };
        match fetch_render_connection_uri(RenderConnectionParams {
            api_key,
            service_id,
            api_base_url: body
                .api_base_url
                .clone()
                .or_else(|| defaults.render_api_base_url.clone()),
        })
        .await
        {
            Ok(uri) => uri,
            Err(err) => {
                return map_provisioning_error("Failed to fetch Render connection URI", err);
            }
        }
    };

    let statements_executed = if body.provision_schema {
        Some(match run_provision_sql(&pg_uri).await {
            Ok(total) => total,
            Err(err) => return map_provisioning_error("Failed to provision Render database", err),
        })
    } else {
        None
    };

    let description = body
        .description
        .clone()
        .or_else(|| Some("Render Postgres database managed via Athena".to_string()));

    let (runtime_registered, catalog_registered) = match register_provisioned_client(
        state.get_ref(),
        &body.client_name,
        description,
        &pg_uri,
        json!({
            "managed_by": "provision_api",
            "provider": "render",
            "service_id": service_id,
            "owner_id": body.owner_id,
            "service_name": body.service_name,
            "plan": body.plan,
            "region": body.region,
            "postgres_version": body.postgres_version,
            "disk_size_gb": body.disk_size_gb,
            "created_service": body.create_service_if_missing,
        }),
        body.register_runtime,
        body.register_catalog,
    )
    .await
    {
        Ok(value) => value,
        Err(resp) => return resp,
    };

    api_success(
        "Provisioned Render database",
        json!({
            "provider": "render",
            "client_name": body.client_name,
            "pg_uri": pg_uri,
            "pipeline": {
                "provision_schema": body.provision_schema,
                "statements_executed": statements_executed,
                "register_runtime": runtime_registered,
                "register_catalog": catalog_registered,
            }
        }),
    )
}

pub(super) fn register(cfg: &mut web::ServiceConfig) {
    cfg.service(admin_provision_neon)
        .service(admin_provision_railway)
        .service(admin_provision_render);
}
