//! Metadata Enricher Service
//!
//! Enriches events with human-readable context (names, dates, addresses)
//! instead of just UUIDs, enabling meaningful notification messages.

use serde_json::{json, Value as JsonValue};
use sqlx::PgPool;
use uuid::Uuid;

use crate::models::EventType;

/// Domain categories for metadata enrichment
#[derive(Debug, Clone, Copy)]
pub enum MetadataDomain {
    Service,
    Project,
    Account,
    Customer,
    Invoice,
    Report,
    Profile,
    Session,
}

/// Metadata enricher service
pub struct MetadataEnricher;

impl MetadataEnricher {
    /// Enrich metadata based on event type and entity
    ///
    /// Returns enriched metadata with human-readable context.
    /// Existing metadata takes precedence over auto-enriched values.
    pub async fn enrich(
        pool: &PgPool,
        event_type: &EventType,
        entity_type: &str,
        entity_id: Uuid,
        existing_metadata: Option<JsonValue>,
    ) -> JsonValue {
        let domain = Self::get_domain(event_type);

        let enriched = if let Some(domain) = domain {
            match domain {
                MetadataDomain::Service => Self::enrich_service(pool, entity_id).await,
                MetadataDomain::Project => Self::enrich_project(pool, entity_id).await,
                MetadataDomain::Account => Self::enrich_account(pool, entity_type, entity_id).await,
                MetadataDomain::Customer => Self::enrich_customer(pool, entity_type, entity_id).await,
                MetadataDomain::Invoice => Self::enrich_invoice(pool, entity_id).await,
                MetadataDomain::Report => Self::enrich_report(pool, entity_id).await,
                MetadataDomain::Profile => Self::enrich_profile(pool, entity_type, entity_id).await,
                MetadataDomain::Session => Self::enrich_session(pool, entity_type, entity_id).await,
            }
        } else {
            json!({})
        };

        // Merge: existing metadata takes precedence
        Self::merge_metadata(existing_metadata, enriched)
    }

    /// Determine the domain based on event type
    fn get_domain(event_type: &EventType) -> Option<MetadataDomain> {
        match event_type {
            // Service events
            EventType::ServiceCreated
            | EventType::ServiceUpdated
            | EventType::ServiceDeleted
            | EventType::ServiceStatusChanged
            | EventType::ServiceAssigned
            | EventType::ServiceUnassigned
            | EventType::ServiceRescheduled => Some(MetadataDomain::Service),

            // Service scope events
            EventType::ServiceScopeCreated
            | EventType::ServiceScopeUpdated
            | EventType::ServiceScopeDeleted
            | EventType::ServiceScopeActivated
            | EventType::ServiceScopeDeactivated
            | EventType::ServiceScopeAreaCreated
            | EventType::ServiceScopeAreaUpdated
            | EventType::ServiceScopeAreaDeleted
            | EventType::ServiceScopeTaskCreated
            | EventType::ServiceScopeTaskUpdated
            | EventType::ServiceScopeTaskDeleted => Some(MetadataDomain::Service),

            // Service task events
            EventType::ServiceTaskCompleted | EventType::ServiceTaskUncompleted => {
                Some(MetadataDomain::Service)
            }

            // Service session events
            EventType::ServiceSessionStarted
            | EventType::ServiceSessionEnded
            | EventType::ServiceSessionReverted => Some(MetadataDomain::Session),

            // Project events
            EventType::ProjectCreated
            | EventType::ProjectUpdated
            | EventType::ProjectDeleted
            | EventType::ProjectStatusChanged
            | EventType::ProjectAssigned
            | EventType::ProjectUnassigned
            | EventType::ProjectRescheduled
            | EventType::ProjectAmountChanged => Some(MetadataDomain::Project),

            // Project scope events
            EventType::ProjectScopeCreated
            | EventType::ProjectScopeUpdated
            | EventType::ProjectScopeDeleted
            | EventType::ProjectScopeActivated
            | EventType::ProjectScopeDeactivated
            | EventType::ProjectScopeCategoryCreated
            | EventType::ProjectScopeCategoryUpdated
            | EventType::ProjectScopeCategoryDeleted
            | EventType::ProjectScopeTaskCreated
            | EventType::ProjectScopeTaskUpdated
            | EventType::ProjectScopeTaskDeleted => Some(MetadataDomain::Project),

            // Project task events
            EventType::ProjectTaskCompleted | EventType::ProjectTaskUncompleted => {
                Some(MetadataDomain::Project)
            }

            // Project session events
            EventType::ProjectSessionStarted
            | EventType::ProjectSessionEnded
            | EventType::ProjectSessionReverted => Some(MetadataDomain::Session),

            // Account events
            EventType::AccountCreated
            | EventType::AccountUpdated
            | EventType::AccountDeleted
            | EventType::AccountStatusChanged
            | EventType::AccountAddressCreated
            | EventType::AccountAddressUpdated
            | EventType::AccountAddressDeleted
            | EventType::AccountAddressPrimaryChanged
            | EventType::AccountContactCreated
            | EventType::AccountContactUpdated
            | EventType::AccountContactDeleted
            | EventType::AccountContactPrimaryChanged => Some(MetadataDomain::Account),

            // Customer events
            EventType::CustomerCreated
            | EventType::CustomerUpdated
            | EventType::CustomerDeleted
            | EventType::CustomerStatusChanged
            | EventType::CustomerAddressCreated
            | EventType::CustomerAddressUpdated
            | EventType::CustomerAddressDeleted
            | EventType::CustomerContactCreated
            | EventType::CustomerContactUpdated
            | EventType::CustomerContactDeleted => Some(MetadataDomain::Customer),

            // Invoice events
            EventType::InvoiceCreated
            | EventType::InvoiceUpdated
            | EventType::InvoiceDeleted
            | EventType::InvoiceStatusChanged
            | EventType::InvoiceSent
            | EventType::InvoicePaid
            | EventType::InvoiceOverdue => Some(MetadataDomain::Invoice),

            // Report events
            EventType::ReportCreated
            | EventType::ReportUpdated
            | EventType::ReportDeleted
            | EventType::ReportSubmitted
            | EventType::ReportApproved => Some(MetadataDomain::Report),

            // Profile events
            EventType::TeamProfileCreated
            | EventType::TeamProfileUpdated
            | EventType::TeamProfileDeleted
            | EventType::TeamProfileRoleChanged
            | EventType::TeamProfileStatusChanged
            | EventType::CustomerProfileCreated
            | EventType::CustomerProfileUpdated
            | EventType::CustomerProfileDeleted
            | EventType::CustomerProfileStatusChanged
            | EventType::CustomerProfileAccessGranted
            | EventType::CustomerProfileAccessRevoked => Some(MetadataDomain::Profile),

            // Session media events
            EventType::SessionNoteCreated
            | EventType::SessionNoteUpdated
            | EventType::SessionNoteDeleted
            | EventType::SessionImageUploaded
            | EventType::SessionImageUpdated
            | EventType::SessionImageDeleted
            | EventType::SessionVideoUploaded
            | EventType::SessionVideoUpdated
            | EventType::SessionVideoDeleted
            | EventType::SessionMediaInternalFlagged => Some(MetadataDomain::Session),

            // Events that don't need enrichment
            _ => None,
        }
    }

    /// Enrich service-related events
    async fn enrich_service(pool: &PgPool, service_id: Uuid) -> JsonValue {
        #[derive(sqlx::FromRow)]
        struct ServiceContext {
            date: chrono::NaiveDate,
            account_name: String,
            customer_name: String,
            street_address: String,
            city: String,
            state: String,
            zip_code: String,
        }

        let result: Option<ServiceContext> = sqlx::query_as(
            r#"
            SELECT s.date, a.name as account_name, c.name as customer_name,
                   aa.street_address, aa.city, aa.state, aa.zip_code
            FROM services s
            JOIN accounts a ON a.id = s.account_id
            JOIN customers c ON c.id = a.customer_id
            JOIN account_addresses aa ON aa.id = s.account_address_id
            WHERE s.id = $1
            "#,
        )
        .bind(service_id)
        .fetch_optional(pool)
        .await
        .ok()
        .flatten();

        match result {
            Some(ctx) => json!({
                "service_id": service_id.to_string(),
                "date": ctx.date.to_string(),
                "account_name": ctx.account_name,
                "customer_name": ctx.customer_name,
                "address": format!("{}, {}, {} {}", ctx.street_address, ctx.city, ctx.state, ctx.zip_code)
            }),
            None => json!({ "service_id": service_id.to_string() }),
        }
    }

    /// Enrich project-related events
    async fn enrich_project(pool: &PgPool, project_id: Uuid) -> JsonValue {
        #[derive(sqlx::FromRow)]
        struct ProjectContext {
            name: String,
            start_date: Option<chrono::NaiveDate>,
            account_name: String,
            customer_name: String,
            street_address: String,
            city: String,
            state: String,
            zip_code: String,
        }

        let result: Option<ProjectContext> = sqlx::query_as(
            r#"
            SELECT p.name, p.start_date, a.name as account_name, c.name as customer_name,
                   aa.street_address, aa.city, aa.state, aa.zip_code
            FROM projects p
            JOIN accounts a ON a.id = p.account_id
            JOIN customers c ON c.id = a.customer_id
            JOIN account_addresses aa ON aa.id = p.account_address_id
            WHERE p.id = $1
            "#,
        )
        .bind(project_id)
        .fetch_optional(pool)
        .await
        .ok()
        .flatten();

        match result {
            Some(ctx) => {
                let mut meta = json!({
                    "project_id": project_id.to_string(),
                    "project_name": ctx.name,
                    "account_name": ctx.account_name,
                    "customer_name": ctx.customer_name,
                    "address": format!("{}, {}, {} {}", ctx.street_address, ctx.city, ctx.state, ctx.zip_code)
                });
                if let Some(date) = ctx.start_date {
                    meta["start_date"] = json!(date.to_string());
                }
                meta
            }
            None => json!({ "project_id": project_id.to_string() }),
        }
    }

    /// Enrich account-related events
    async fn enrich_account(pool: &PgPool, entity_type: &str, entity_id: Uuid) -> JsonValue {
        // Handle different account-related entity types
        let account_id = match entity_type {
            "account" => entity_id,
            "account_address" => {
                let id: Option<Uuid> = sqlx::query_scalar(
                    "SELECT account_id FROM account_addresses WHERE id = $1",
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();
                match id {
                    Some(id) => id,
                    None => return json!({ "entity_id": entity_id.to_string() }),
                }
            }
            "account_contact" => {
                let id: Option<Uuid> = sqlx::query_scalar(
                    "SELECT account_id FROM account_contacts WHERE id = $1",
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();
                match id {
                    Some(id) => id,
                    None => return json!({ "entity_id": entity_id.to_string() }),
                }
            }
            _ => return json!({ "entity_id": entity_id.to_string() }),
        };

        #[derive(sqlx::FromRow)]
        struct AccountContext {
            name: String,
            customer_name: String,
        }

        let result: Option<AccountContext> = sqlx::query_as(
            r#"
            SELECT a.name, c.name as customer_name
            FROM accounts a
            JOIN customers c ON c.id = a.customer_id
            WHERE a.id = $1
            "#,
        )
        .bind(account_id)
        .fetch_optional(pool)
        .await
        .ok()
        .flatten();

        match result {
            Some(ctx) => json!({
                "account_id": account_id.to_string(),
                "account_name": ctx.name,
                "customer_name": ctx.customer_name
            }),
            None => json!({ "account_id": account_id.to_string() }),
        }
    }

    /// Enrich customer-related events
    async fn enrich_customer(pool: &PgPool, entity_type: &str, entity_id: Uuid) -> JsonValue {
        // Handle different customer-related entity types
        let customer_id = match entity_type {
            "customer" => entity_id,
            "customer_address" => {
                let id: Option<Uuid> = sqlx::query_scalar(
                    "SELECT customer_id FROM customer_addresses WHERE id = $1",
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();
                match id {
                    Some(id) => id,
                    None => return json!({ "entity_id": entity_id.to_string() }),
                }
            }
            "customer_contact" => {
                let id: Option<Uuid> = sqlx::query_scalar(
                    "SELECT customer_id FROM customer_contacts WHERE id = $1",
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();
                match id {
                    Some(id) => id,
                    None => return json!({ "entity_id": entity_id.to_string() }),
                }
            }
            _ => return json!({ "entity_id": entity_id.to_string() }),
        };

        let name: Option<String> =
            sqlx::query_scalar("SELECT name FROM customers WHERE id = $1")
                .bind(customer_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();

        match name {
            Some(name) => json!({
                "customer_id": customer_id.to_string(),
                "customer_name": name
            }),
            None => json!({ "customer_id": customer_id.to_string() }),
        }
    }

    /// Enrich invoice-related events
    async fn enrich_invoice(pool: &PgPool, invoice_id: Uuid) -> JsonValue {
        #[derive(sqlx::FromRow)]
        struct InvoiceContext {
            invoice_number: Option<String>,
            total: rust_decimal::Decimal,
            customer_name: String,
        }

        let result: Option<InvoiceContext> = sqlx::query_as(
            r#"
            SELECT i.invoice_number, i.total, c.name as customer_name
            FROM invoices i
            JOIN customers c ON c.id = i.customer_id
            WHERE i.id = $1
            "#,
        )
        .bind(invoice_id)
        .fetch_optional(pool)
        .await
        .ok()
        .flatten();

        match result {
            Some(ctx) => json!({
                "invoice_id": invoice_id.to_string(),
                "invoice_number": ctx.invoice_number,
                "total": ctx.total.to_string(),
                "customer_name": ctx.customer_name
            }),
            None => json!({ "invoice_id": invoice_id.to_string() }),
        }
    }

    /// Enrich report-related events
    async fn enrich_report(pool: &PgPool, report_id: Uuid) -> JsonValue {
        #[derive(sqlx::FromRow)]
        struct ReportContext {
            team_first_name: String,
            team_last_name: String,
            start_date: chrono::NaiveDate,
            end_date: chrono::NaiveDate,
        }

        let result: Option<ReportContext> = sqlx::query_as(
            r#"
            SELECT tp.first_name as team_first_name, tp.last_name as team_last_name,
                   r.start_date, r.end_date
            FROM reports r
            JOIN team_profiles tp ON tp.id = r.team_profile_id
            WHERE r.id = $1
            "#,
        )
        .bind(report_id)
        .fetch_optional(pool)
        .await
        .ok()
        .flatten();

        match result {
            Some(ctx) => json!({
                "report_id": report_id.to_string(),
                "team_member": format!("{} {}", ctx.team_first_name, ctx.team_last_name),
                "period": format!("{} - {}", ctx.start_date, ctx.end_date)
            }),
            None => json!({ "report_id": report_id.to_string() }),
        }
    }

    /// Enrich profile-related events
    async fn enrich_profile(pool: &PgPool, entity_type: &str, entity_id: Uuid) -> JsonValue {
        match entity_type {
            "team_profile" => {
                #[derive(sqlx::FromRow)]
                struct TeamProfileContext {
                    first_name: String,
                    last_name: String,
                    email: Option<String>,
                }

                let result: Option<TeamProfileContext> = sqlx::query_as(
                    "SELECT first_name, last_name, email FROM team_profiles WHERE id = $1",
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();

                match result {
                    Some(ctx) => json!({
                        "profile_id": entity_id.to_string(),
                        "profile_type": "team",
                        "name": format!("{} {}", ctx.first_name, ctx.last_name),
                        "email": ctx.email
                    }),
                    None => json!({ "profile_id": entity_id.to_string(), "profile_type": "team" }),
                }
            }
            "customer_profile" => {
                #[derive(sqlx::FromRow)]
                struct CustomerProfileContext {
                    first_name: String,
                    last_name: String,
                    email: Option<String>,
                    customer_name: String,
                }

                let result: Option<CustomerProfileContext> = sqlx::query_as(
                    r#"
                    SELECT cp.first_name, cp.last_name, cp.email, c.name as customer_name
                    FROM customer_profiles cp
                    JOIN customers c ON c.id = cp.customer_id
                    WHERE cp.id = $1
                    "#,
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();

                match result {
                    Some(ctx) => json!({
                        "profile_id": entity_id.to_string(),
                        "profile_type": "customer",
                        "name": format!("{} {}", ctx.first_name, ctx.last_name),
                        "email": ctx.email,
                        "customer_name": ctx.customer_name
                    }),
                    None => json!({ "profile_id": entity_id.to_string(), "profile_type": "customer" }),
                }
            }
            _ => json!({ "profile_id": entity_id.to_string() }),
        }
    }

    /// Enrich session-related events
    async fn enrich_session(pool: &PgPool, entity_type: &str, entity_id: Uuid) -> JsonValue {
        match entity_type {
            "service_session" => {
                #[derive(sqlx::FromRow)]
                struct SessionContext {
                    date: chrono::NaiveDate,
                    account_name: String,
                    customer_name: String,
                    street_address: String,
                    city: String,
                    state: String,
                    zip_code: String,
                }

                let result: Option<SessionContext> = sqlx::query_as(
                    r#"
                    SELECT ss.date, a.name as account_name, c.name as customer_name,
                           aa.street_address, aa.city, aa.state, aa.zip_code
                    FROM service_sessions ss
                    JOIN accounts a ON a.id = ss.account_id
                    JOIN customers c ON c.id = ss.customer_id
                    JOIN account_addresses aa ON aa.id = ss.account_address_id
                    WHERE ss.id = $1
                    "#,
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();

                match result {
                    Some(ctx) => json!({
                        "session_id": entity_id.to_string(),
                        "session_type": "service",
                        "date": ctx.date.to_string(),
                        "account_name": ctx.account_name,
                        "customer_name": ctx.customer_name,
                        "address": format!("{}, {}, {} {}", ctx.street_address, ctx.city, ctx.state, ctx.zip_code)
                    }),
                    None => json!({ "session_id": entity_id.to_string(), "session_type": "service" }),
                }
            }
            "project_session" => {
                #[derive(sqlx::FromRow)]
                struct SessionContext {
                    date: chrono::NaiveDate,
                    project_name: String,
                    account_name: String,
                    customer_name: String,
                    street_address: String,
                    city: String,
                    state: String,
                    zip_code: String,
                }

                let result: Option<SessionContext> = sqlx::query_as(
                    r#"
                    SELECT ps.date, p.name as project_name, a.name as account_name, c.name as customer_name,
                           aa.street_address, aa.city, aa.state, aa.zip_code
                    FROM project_sessions ps
                    JOIN projects p ON p.id = ps.project_id
                    JOIN accounts a ON a.id = ps.account_id
                    JOIN customers c ON c.id = ps.customer_id
                    JOIN account_addresses aa ON aa.id = ps.account_address_id
                    WHERE ps.id = $1
                    "#,
                )
                .bind(entity_id)
                .fetch_optional(pool)
                .await
                .ok()
                .flatten();

                match result {
                    Some(ctx) => json!({
                        "session_id": entity_id.to_string(),
                        "session_type": "project",
                        "date": ctx.date.to_string(),
                        "project_name": ctx.project_name,
                        "account_name": ctx.account_name,
                        "customer_name": ctx.customer_name,
                        "address": format!("{}, {}, {} {}", ctx.street_address, ctx.city, ctx.state, ctx.zip_code)
                    }),
                    None => json!({ "session_id": entity_id.to_string(), "session_type": "project" }),
                }
            }
            _ => json!({ "session_id": entity_id.to_string() }),
        }
    }

    /// Merge existing metadata with enriched metadata
    /// Existing values take precedence over enriched values
    fn merge_metadata(existing: Option<JsonValue>, enriched: JsonValue) -> JsonValue {
        match existing {
            Some(JsonValue::Object(mut existing_map)) => {
                if let JsonValue::Object(enriched_map) = enriched {
                    // Add enriched values that don't exist in existing
                    for (key, value) in enriched_map {
                        existing_map.entry(key).or_insert(value);
                    }
                }
                JsonValue::Object(existing_map)
            }
            Some(other) => other, // Non-object existing metadata is preserved as-is
            None => enriched,
        }
    }
}
