use crate::{
    PipelineDefinition, PipelineTemplateRecord, PipelineTemplateSummary,
    PipelineTemplateSummaryMerge, ensure_pipeline_template_table, list_pipeline_templates,
    merge_template_summaries, pipeline_definition_map_from_records,
    runtime_template_candidates_from_records,
};
use serde::Serialize;
use sqlx::PgPool;
use std::collections::HashMap;
use thiserror::Error;

/// Errors returned while loading database-backed pipeline templates for runtime use.
#[derive(Debug, Error)]
pub enum PipelineTemplateCatalogError {
    #[error("failed to initialize pipeline template store: {0}")]
    Initialize(#[source] sqlx::Error),
    #[error("failed to load pipeline templates: {0}")]
    Load(#[source] sqlx::Error),
    #[error("{0}")]
    InvalidDefinition(String),
}

/// Response envelope returned by `GET /pipelines/templates`.
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct PipelineTemplateListResponse {
    pub templates: Vec<PipelineTemplateSummary>,
}

impl PipelineTemplateListResponse {
    /// Builds the canonical list-templates response body.
    pub fn new(templates: Vec<PipelineTemplateSummary>) -> Self {
        Self { templates }
    }
}

/// Loads persisted pipeline template records from the optional logging pool.
///
/// When no pool is available, the runtime behaves as if there are no managed templates.
pub async fn load_pipeline_template_records(
    pool: Option<&PgPool>,
) -> Result<Vec<PipelineTemplateRecord>, PipelineTemplateCatalogError> {
    let Some(pool) = pool else {
        return Ok(Vec::new());
    };

    ensure_pipeline_template_table(pool)
        .await
        .map_err(PipelineTemplateCatalogError::Initialize)?;
    list_pipeline_templates(pool)
        .await
        .map_err(PipelineTemplateCatalogError::Load)
}

/// Loads the database-backed template definition map used by pipeline reference resolution.
pub async fn load_pipeline_definition_map(
    pool: Option<&PgPool>,
) -> Result<HashMap<String, PipelineDefinition>, PipelineTemplateCatalogError> {
    pipeline_definition_map_from_records(load_pipeline_template_records(pool).await?)
        .map_err(PipelineTemplateCatalogError::InvalidDefinition)
}

/// Loads the merged template catalog used by `GET /pipelines/templates`.
pub async fn load_pipeline_template_summary_merge(
    pool: Option<&PgPool>,
    registry: &HashMap<String, PipelineDefinition>,
) -> Result<PipelineTemplateSummaryMerge, PipelineTemplateCatalogError> {
    let runtime_templates =
        runtime_template_candidates_from_records(load_pipeline_template_records(pool).await?)
            .map_err(PipelineTemplateCatalogError::InvalidDefinition)?;
    Ok(merge_template_summaries(registry, &runtime_templates))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{SinkConfig, SourceConfig};
    use serde_json::json;

    #[tokio::test]
    async fn load_pipeline_definition_map_without_pool_returns_empty_map() {
        let definitions = load_pipeline_definition_map(None)
            .await
            .expect("load without pool should succeed");
        assert!(definitions.is_empty());
    }

    #[tokio::test]
    async fn load_pipeline_template_summary_merge_without_pool_keeps_registry_templates() {
        let registry = HashMap::from([(
            "boot_example".to_string(),
            PipelineDefinition {
                source: SourceConfig {
                    table_name: "events_source".to_string(),
                    ..Default::default()
                },
                transform: None,
                sink: SinkConfig {
                    table_name: "events_sink".to_string(),
                    client: None,
                },
            },
        )]);

        let merge = load_pipeline_template_summary_merge(None, &registry)
            .await
            .expect("load without pool should succeed");

        assert!(merge.shadowed_template_names.is_empty());
        assert_eq!(merge.templates.len(), 1);
        assert_eq!(merge.templates[0].name, "boot_example");
    }

    #[test]
    fn pipeline_template_list_response_serializes_templates_field() {
        let response = PipelineTemplateListResponse::new(Vec::new());
        assert_eq!(
            serde_json::to_value(response).expect("serialize response"),
            json!({ "templates": [] })
        );
    }
}
