use std::collections::HashMap;
use windmill_common::error::Result;
use windmill_common::DB;

use serde::Serialize;

use crate::native_triggers::{
    decrypt_oauth_data, list_native_triggers, update_native_trigger_error,
    update_native_trigger_service_config, External, ServiceName,
};

#[derive(Debug, Serialize)]
pub struct TriggerSyncInfo {
    pub external_id: String,
    pub script_path: String,
    pub action: SyncAction,
}

#[derive(Debug, Serialize)]
pub enum SyncAction {
    ErrorSet(String),
    ErrorCleared,
    ConfigUpdated,
}

#[derive(Debug, Serialize)]
pub struct SyncError {
    pub resource_path: String,
    pub error_message: String,
    pub error_type: String,
}

#[derive(Debug)]
pub struct BackgroundSyncResult {
    pub workspaces_processed: usize,
    pub total_synced: usize,
    pub total_errors: usize,
    pub service_results: HashMap<ServiceName, ServiceSyncResult>,
}

#[derive(Debug)]
pub struct ServiceSyncResult {
    pub synced_triggers: Vec<TriggerSyncInfo>,
    pub errors: Vec<SyncError>,
}

pub async fn sync_all_triggers(db: &DB) -> Result<BackgroundSyncResult> {
    tracing::info!("Starting native triggers sync");

    let mut service_results: HashMap<ServiceName, ServiceSyncResult> = HashMap::new();
    let mut total_synced = 0;
    let mut total_errors = 0;
    let mut workspaces_processed = 0;

    // Sync all registered services
    // Each service only syncs workspaces that have the corresponding integration configured
    #[cfg(feature = "native_trigger")]
    {
        use crate::native_triggers::nextcloud::NextCloud;

        let (service_name, result) = sync_service_triggers(db, NextCloud).await;
        total_synced += result.synced_triggers.len();
        total_errors += result.errors.len();
        service_results.insert(service_name, result);

        // Add new services here:
        // use crate::native_triggers::newservice::NewService;
        // let (service_name, result) = sync_service_triggers(db, NewService).await;
        // total_synced += result.synced_triggers.len();
        // total_errors += result.errors.len();
        // service_results.insert(service_name, result);
    }

    // Count unique workspaces processed across all services
    for result in service_results.values() {
        workspaces_processed += result
            .synced_triggers
            .iter()
            .map(|t| &t.external_id)
            .collect::<std::collections::HashSet<_>>()
            .len();
    }

    let result =
        BackgroundSyncResult { workspaces_processed, total_synced, total_errors, service_results };

    tracing::info!(
        "Completed native triggers sync: {} updated, {} errors",
        result.total_synced,
        result.total_errors
    );

    Ok(result)
}

async fn sync_service_triggers<T: External>(
    db: &DB,
    handler: T,
) -> (ServiceName, ServiceSyncResult) {
    let mut all_synced_triggers = Vec::new();
    let mut all_errors = Vec::new();

    // Only sync workspaces that have the corresponding integration configured
    let workspaces_with_integration = match sqlx::query_scalar!(
        r#"
        SELECT wi.workspace_id
        FROM workspace_integrations wi
        JOIN workspace w ON w.id = wi.workspace_id
        WHERE wi.service_name = $1
          AND wi.oauth_data IS NOT NULL
          AND w.deleted = false
        "#,
        T::SERVICE_NAME as ServiceName
    )
    .fetch_all(db)
    .await
    {
        Ok(workspaces) => workspaces,
        Err(e) => {
            tracing::error!(
                "Error querying workspaces with {} integration: {:#}",
                T::SERVICE_NAME.as_str(),
                e
            );
            all_errors.push(SyncError {
                resource_path: "database".to_string(),
                error_message: format!("Failed to query workspaces: {}", e),
                error_type: "database_error".to_string(),
            });
            return (
                T::SERVICE_NAME,
                ServiceSyncResult { synced_triggers: Vec::new(), errors: all_errors },
            );
        }
    };

    if workspaces_with_integration.is_empty() {
        tracing::debug!(
            "No workspaces with {} integration configured, skipping sync",
            T::SERVICE_NAME.as_str()
        );
        return (
            T::SERVICE_NAME,
            ServiceSyncResult { synced_triggers: Vec::new(), errors: Vec::new() },
        );
    }

    tracing::info!(
        "Found {} workspaces with {} integration configured",
        workspaces_with_integration.len(),
        T::SERVICE_NAME.as_str()
    );

    for workspace_id in workspaces_with_integration {
        let sync_result = sync_workspace_triggers::<T>(db, &workspace_id, &handler).await;

        match sync_result {
            Ok((synced_triggers, errors)) => {
                all_synced_triggers.extend(synced_triggers);
                all_errors.extend(errors);
            }
            Err(e) => {
                tracing::error!(
                    "Error syncing {} triggers for workspace {}: {:#}",
                    T::SERVICE_NAME.as_str(),
                    workspace_id,
                    e
                );
                all_errors.push(SyncError {
                    resource_path: format!("workspace:{}", workspace_id),
                    error_message: format!("Failed to sync workspace: {}", e),
                    error_type: "workspace_sync_error".to_string(),
                });
            }
        }
    }

    (
        T::SERVICE_NAME,
        ServiceSyncResult { synced_triggers: all_synced_triggers, errors: all_errors },
    )
}

#[cfg(feature = "native_trigger")]
pub async fn sync_workspace_triggers<T: External>(
    db: &DB,
    workspace_id: &str,
    handler: &T,
) -> Result<(Vec<TriggerSyncInfo>, Vec<SyncError>)> {
    tracing::info!(
        "Syncing {} triggers for workspace '{}'",
        T::SERVICE_NAME.as_str(),
        workspace_id
    );

    let windmill_triggers =
        list_native_triggers(db, workspace_id, T::SERVICE_NAME, None, None, None, None).await?;

    if windmill_triggers.is_empty() {
        tracing::info!(
            "No {} triggers found for workspace '{}'",
            T::SERVICE_NAME.as_str(),
            workspace_id
        );
        return Ok((Vec::new(), Vec::new()));
    }

    let mut all_synced_triggers = Vec::new();
    let mut all_sync_errors = Vec::new();

    let oauth_data = {
        match decrypt_oauth_data(db, db, workspace_id, T::SERVICE_NAME).await {
            Ok(oauth_data) => oauth_data,
            Err(e) => {
                tracing::error!(
                    "Failed to get workspace integration OAuth data for {}: {}",
                    workspace_id,
                    e
                );
                all_sync_errors.push(SyncError {
                    resource_path: format!("workspace:{}", workspace_id),
                    error_message: format!("Failed to get workspace integration OAuth data: {}", e),
                    error_type: "oauth_error".to_string(),
                });
                return Ok((Vec::new(), all_sync_errors));
            }
        }
    };

    let mut tx = db.begin().await?;
    let external_triggers = match handler
        .list_all(workspace_id, &oauth_data, db, &mut tx)
        .await
    {
        Ok(triggers) => triggers,
        Err(e) => {
            tracing::error!(
                "Failed to fetch external triggers for {}: {}",
                workspace_id,
                e
            );
            all_sync_errors.push(SyncError {
                resource_path: format!("workspace:{}", workspace_id),
                error_message: format!("Failed to fetch external triggers: {}", e),
                error_type: "external_service_error".to_string(),
            });
            return Ok((Vec::new(), all_sync_errors));
        }
    };
    tx.commit().await?;

    // Build a map of external trigger IDs to their data
    let mut external_trigger_map: HashMap<String, &T::TriggerData> = HashMap::new();
    for external_trigger in &external_triggers {
        let external_id = handler.get_external_id_from_trigger_data(external_trigger);
        external_trigger_map.insert(external_id, external_trigger);
    }

    for trigger in &windmill_triggers {
        if !external_trigger_map.contains_key(&trigger.external_id) {
            // Trigger no longer exists on external service - set error
            let error_msg = "Trigger no longer exists on external service".to_string();

            if trigger.error.as_deref() != Some(&error_msg) {
                tracing::info!(
                    "Trigger (external_id: '{}', script_path: '{}') no longer exists in external service, setting error",
                    trigger.external_id,
                    trigger.script_path
                );

                match update_native_trigger_error(
                    db,
                    workspace_id,
                    T::SERVICE_NAME,
                    &trigger.external_id,
                    Some(&error_msg),
                )
                .await
                {
                    Ok(()) => {
                        all_synced_triggers.push(TriggerSyncInfo {
                            external_id: trigger.external_id.clone(),
                            script_path: trigger.script_path.clone(),
                            action: SyncAction::ErrorSet(error_msg),
                        });
                    }
                    Err(e) => {
                        tracing::error!(
                            "Failed to update error for trigger (external_id: '{}'): {}",
                            trigger.external_id,
                            e
                        );
                        all_sync_errors.push(SyncError {
                            resource_path: format!("workspace:{}", workspace_id),
                            error_message: format!(
                                "Failed to update error for trigger (external_id: '{}'): {}",
                                trigger.external_id, e
                            ),
                            error_type: "database_update_error".to_string(),
                        });
                    }
                }
            }
        } else {
            // Trigger exists on external service
            let external_trigger_data = external_trigger_map.get(&trigger.external_id).unwrap();

            // Clear error if it was set
            if trigger.error.is_some() {
                tracing::info!(
                    "Trigger (external_id: '{}', script_path: '{}') exists on external service, clearing error",
                    trigger.external_id,
                    trigger.script_path
                );

                match update_native_trigger_error(
                    db,
                    workspace_id,
                    T::SERVICE_NAME,
                    &trigger.external_id,
                    None,
                )
                .await
                {
                    Ok(()) => {
                        all_synced_triggers.push(TriggerSyncInfo {
                            external_id: trigger.external_id.clone(),
                            script_path: trigger.script_path.clone(),
                            action: SyncAction::ErrorCleared,
                        });
                    }
                    Err(e) => {
                        tracing::error!(
                            "Failed to clear error for trigger (external_id: '{}'): {}",
                            trigger.external_id,
                            e
                        );
                        all_sync_errors.push(SyncError {
                            resource_path: format!("workspace:{}", workspace_id),
                            error_message: format!(
                                "Failed to clear error for trigger (external_id: '{}'): {}",
                                trigger.external_id, e
                            ),
                            error_type: "database_update_error".to_string(),
                        });
                    }
                }
            }

            // Compare service_config and update if different
            let external_service_config =
                handler.extract_service_config_from_trigger_data(external_trigger_data)?;
            let stored_service_config = trigger
                .service_config
                .clone()
                .unwrap_or(serde_json::Value::Null);

            if external_service_config != stored_service_config {
                tracing::info!(
                    "Trigger (external_id: '{}', script_path: '{}') config differs from external service, updating local config",
                    trigger.external_id,
                    trigger.script_path
                );

                match update_native_trigger_service_config(
                    db,
                    workspace_id,
                    T::SERVICE_NAME,
                    &trigger.external_id,
                    &external_service_config,
                )
                .await
                {
                    Ok(()) => {
                        all_synced_triggers.push(TriggerSyncInfo {
                            external_id: trigger.external_id.clone(),
                            script_path: trigger.script_path.clone(),
                            action: SyncAction::ConfigUpdated,
                        });
                    }
                    Err(e) => {
                        tracing::error!(
                            "Failed to update config for trigger (external_id: '{}'): {}",
                            trigger.external_id,
                            e
                        );
                        all_sync_errors.push(SyncError {
                            resource_path: format!("workspace:{}", workspace_id),
                            error_message: format!(
                                "Failed to update config for trigger (external_id: '{}'): {}",
                                trigger.external_id, e
                            ),
                            error_type: "database_update_error".to_string(),
                        });
                    }
                }
            } else {
                tracing::info!(
                    "Trigger (external_id: '{}', script_path: '{}') config is the same as external service, no update needed",
                    trigger.external_id,
                    trigger.script_path
                );
            }
        }
    }

    tracing::info!(
        "Sync completed for {} in workspace '{}'. Updated: {}, Errors: {}",
        T::SERVICE_NAME.as_str(),
        workspace_id,
        all_synced_triggers.len(),
        all_sync_errors.len()
    );

    Ok((all_synced_triggers, all_sync_errors))
}
