//! Notification Processor Service
//!
//! Processes events and creates notifications based on matching rules.
//! Handles recipient determination, template rendering, and notification creation.

use anyhow::Result;
use chrono::Utc;
use serde_json::Value as JsonValue;
use sqlx::PgPool;
use uuid::Uuid;

use crate::models::{
    DeliveryStatus, Event, Notification, NotificationChannel, NotificationDelivery,
    NotificationRule, NotificationStatus,
};

/// Notification processor service
pub struct NotificationProcessor;

impl NotificationProcessor {
    /// Process an event and create notifications based on matching rules
    ///
    /// # Arguments
    /// * `pool` - Database connection pool
    /// * `event` - The event to process
    ///
    /// # Returns
    /// * `Ok(Vec<Notification>)` - Created notifications
    pub async fn process_event(pool: &PgPool, event: &Event) -> Result<Vec<Notification>> {
        // Find active rules matching this event type
        let rules = Self::find_matching_rules(pool, &event.event_type).await?;

        if rules.is_empty() {
            tracing::debug!(
                event_id = %event.id,
                event_type = ?event.event_type,
                "No matching notification rules for event"
            );
            return Ok(vec![]);
        }

        let mut notifications = Vec::new();

        for rule in rules {
            // Check if rule conditions match the event metadata
            if !Self::matches_conditions(&rule, event) {
                continue;
            }

            // Determine recipients based on rule targeting
            let recipients = Self::get_recipients(pool, &rule).await?;

            // Render templates with event context
            let subject = Self::render_template(
                rule.subject_template.as_deref().unwrap_or("Notification"),
                event,
            );
            let body = Self::render_template(
                rule.body_template.as_deref().unwrap_or("You have a new notification."),
                event,
            );

            // Generate action URL
            let action_url = Self::generate_action_url(&event.entity_type, event.entity_id);

            // Create notification for each recipient
            for (recipient_type, recipient_id) in recipients {
                let notification = Self::create_notification(
                    pool,
                    &rule,
                    event,
                    &recipient_type,
                    recipient_id,
                    &subject,
                    &body,
                    action_url.as_deref(),
                )
                .await?;

                // Create delivery records for each channel
                Self::create_delivery_records(pool, &notification, &rule).await?;

                notifications.push(notification);
            }
        }

        tracing::info!(
            event_id = %event.id,
            notification_count = notifications.len(),
            "Processed event and created notifications"
        );

        Ok(notifications)
    }

    /// Find active rules matching the given event type
    async fn find_matching_rules(pool: &PgPool, event_type: &crate::models::EventType) -> Result<Vec<NotificationRule>> {
        let event_type_str = format!("{:?}", event_type);

        let rules: Vec<NotificationRule> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, name, description, is_active, event_types,
                   channels, target_roles, conditions, subject_template, body_template
            FROM notification_rules
            WHERE is_active = true
            AND event_types @> $1::jsonb
            "#,
        )
        .bind(serde_json::json!([event_type_str]))
        .fetch_all(pool)
        .await?;

        Ok(rules)
    }

    /// Check if rule conditions match event metadata
    fn matches_conditions(rule: &NotificationRule, event: &Event) -> bool {
        let Some(conditions) = &rule.conditions else {
            return true; // No conditions means always match
        };

        let Some(metadata) = &event.metadata else {
            return false; // Has conditions but no metadata to check
        };

        // Simple JSON path matching
        // Conditions format: { "key": "expected_value" } or { "key": { "$in": ["val1", "val2"] } }
        if let (JsonValue::Object(cond_map), JsonValue::Object(meta_map)) = (conditions, metadata) {
            for (key, expected) in cond_map {
                let actual = meta_map.get(key);

                match (expected, actual) {
                    // Direct value comparison
                    (JsonValue::String(exp), Some(JsonValue::String(act))) if exp == act => continue,
                    (JsonValue::Number(exp), Some(JsonValue::Number(act))) if exp == act => continue,
                    (JsonValue::Bool(exp), Some(JsonValue::Bool(act))) if exp == act => continue,

                    // $in operator
                    (JsonValue::Object(op), Some(actual_val)) => {
                        if let Some(JsonValue::Array(allowed)) = op.get("$in") {
                            if allowed.contains(actual_val) {
                                continue;
                            }
                        }
                        return false;
                    }

                    // No match
                    _ => return false,
                }
            }
            true
        } else {
            false
        }
    }

    /// Determine recipients based on rule targeting
    async fn get_recipients(pool: &PgPool, rule: &NotificationRule) -> Result<Vec<(String, Uuid)>> {
        let mut recipients = Vec::new();

        // Get specific team profile targets
        let team_targets: Vec<Uuid> = sqlx::query_scalar(
            "SELECT team_profile_id FROM notification_rule_team_profiles WHERE rule_id = $1",
        )
        .bind(rule.base.id)
        .fetch_all(pool)
        .await?;

        for id in team_targets {
            recipients.push(("team_profile".to_string(), id));
        }

        // Get specific customer profile targets
        let customer_targets: Vec<Uuid> = sqlx::query_scalar(
            "SELECT customer_profile_id FROM notification_rule_customer_profiles WHERE rule_id = $1",
        )
        .bind(rule.base.id)
        .fetch_all(pool)
        .await?;

        for id in customer_targets {
            recipients.push(("customer_profile".to_string(), id));
        }

        // Get team profiles by role if target_roles is specified
        if let Some(target_roles) = &rule.target_roles {
            if let JsonValue::Array(roles) = target_roles {
                let role_strings: Vec<&str> = roles
                    .iter()
                    .filter_map(|v| v.as_str())
                    .collect();

                if !role_strings.is_empty() {
                    // Query team profiles with matching roles
                    let role_targets: Vec<Uuid> = sqlx::query_scalar(
                        r#"
                        SELECT id FROM team_profiles
                        WHERE status = 'ACTIVE'
                        AND role::text = ANY($1)
                        "#,
                    )
                    .bind(&role_strings)
                    .fetch_all(pool)
                    .await?;

                    for id in role_targets {
                        // Avoid duplicates
                        if !recipients.iter().any(|(t, i)| t == "team_profile" && *i == id) {
                            recipients.push(("team_profile".to_string(), id));
                        }
                    }
                }
            }
        }

        Ok(recipients)
    }

    /// Render a template with event context
    ///
    /// Supports simple variable substitution: {{variable_name}}
    /// Variables are pulled from event metadata
    fn render_template(template: &str, event: &Event) -> String {
        let mut result = template.to_string();

        // Built-in variables
        result = result.replace("{{event_type}}", &format!("{:?}", event.event_type));
        result = result.replace("{{entity_type}}", &event.entity_type);
        result = result.replace("{{entity_id}}", &event.entity_id.to_string());
        result = result.replace("{{timestamp}}", &event.timestamp.to_rfc3339());

        // Metadata variables - simple loop-based replacement
        if let Some(JsonValue::Object(meta)) = &event.metadata {
            for (key, value) in meta {
                let placeholder = format!("{{{{{}}}}}", key);
                let replacement = match value {
                    JsonValue::String(s) => s.clone(),
                    JsonValue::Number(n) => n.to_string(),
                    JsonValue::Bool(b) => b.to_string(),
                    _ => continue,
                };
                result = result.replace(&placeholder, &replacement);
            }
        }

        result
    }

    /// Generate action URL based on entity type
    fn generate_action_url(entity_type: &str, entity_id: Uuid) -> Option<String> {
        let path = match entity_type {
            "service" => format!("/services/{}", entity_id),
            "project" => format!("/projects/{}", entity_id),
            "account" => format!("/accounts/{}", entity_id),
            "customer" => format!("/customers/{}", entity_id),
            "invoice" => format!("/invoices/{}", entity_id),
            "report" => format!("/reports/{}", entity_id),
            "service_session" => format!("/sessions/service/{}", entity_id),
            "project_session" => format!("/sessions/project/{}", entity_id),
            "team_profile" => format!("/team/{}", entity_id),
            "customer_profile" => format!("/customer-profiles/{}", entity_id),
            _ => return None,
        };

        Some(path)
    }

    /// Create a notification record
    async fn create_notification(
        pool: &PgPool,
        rule: &NotificationRule,
        event: &Event,
        recipient_type: &str,
        recipient_id: Uuid,
        subject: &str,
        body: &str,
        action_url: Option<&str>,
    ) -> Result<Notification> {
        let now = Utc::now();

        let notification: Notification = sqlx::query_as(
            r#"
            INSERT INTO notifications (
                id, created_at, updated_at, recipient_type, recipient_id, rule_id, event_id,
                status, subject, body, action_url, read_at, metadata
            )
            VALUES (gen_random_uuid(), $1, $1, $2, $3, $4, $5, $6, $7, $8, $9, NULL, NULL)
            RETURNING id, created_at, updated_at, recipient_type, recipient_id, rule_id, event_id,
                      status, subject, body, action_url, read_at, metadata
            "#,
        )
        .bind(now)
        .bind(recipient_type)
        .bind(recipient_id)
        .bind(rule.base.id)
        .bind(event.id)
        .bind(NotificationStatus::Pending)
        .bind(subject)
        .bind(body)
        .bind(action_url)
        .fetch_one(pool)
        .await?;

        Ok(notification)
    }

    /// Create delivery records for each channel in the rule
    async fn create_delivery_records(
        pool: &PgPool,
        notification: &Notification,
        rule: &NotificationRule,
    ) -> Result<Vec<NotificationDelivery>> {
        let mut deliveries = Vec::new();
        let now = Utc::now();

        // Parse channels from JSONB
        let channels: Vec<NotificationChannel> = if let JsonValue::Array(arr) = &rule.channels {
            arr.iter()
                .filter_map(|v| v.as_str())
                .filter_map(|s| match s.to_uppercase().as_str() {
                    "IN_APP" | "INAPP" => Some(NotificationChannel::InApp),
                    "EMAIL" => Some(NotificationChannel::Email),
                    "SMS" => Some(NotificationChannel::Sms),
                    _ => None,
                })
                .collect()
        } else {
            vec![NotificationChannel::InApp] // Default to in-app
        };

        for channel in channels {
            let delivery: NotificationDelivery = sqlx::query_as(
                r#"
                INSERT INTO notification_deliveries (
                    id, created_at, updated_at, notification_id, channel, status, attempts,
                    last_attempt_at, sent_at, delivered_at, error_message, external_id, metadata
                )
                VALUES (gen_random_uuid(), $1, $1, $2, $3, $4, 0, NULL, NULL, NULL, NULL, NULL, NULL)
                RETURNING id, created_at, updated_at, notification_id, channel, status, attempts,
                          last_attempt_at, sent_at, delivered_at, error_message, external_id, metadata
                "#,
            )
            .bind(now)
            .bind(notification.base.id)
            .bind(&channel)
            .bind(DeliveryStatus::Pending)
            .fetch_one(pool)
            .await?;

            deliveries.push(delivery);
        }

        Ok(deliveries)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_render_template() {
        let event = Event::new(
            crate::models::EventType::ServiceCreated,
            "service",
            Uuid::new_v4(),
            None,
            None,
            Some(json!({
                "account_name": "Acme Corp",
                "customer_name": "John Doe",
                "date": "2026-01-10"
            })),
        );

        let template = "Service for {{customer_name}} at {{account_name}} on {{date}}";
        let result = NotificationProcessor::render_template(template, &event);

        assert_eq!(result, "Service for John Doe at Acme Corp on 2026-01-10");
    }

    #[test]
    fn test_generate_action_url() {
        let id = Uuid::new_v4();

        assert_eq!(
            NotificationProcessor::generate_action_url("service", id),
            Some(format!("/services/{}", id))
        );
        assert_eq!(
            NotificationProcessor::generate_action_url("project", id),
            Some(format!("/projects/{}", id))
        );
        assert_eq!(
            NotificationProcessor::generate_action_url("unknown", id),
            None
        );
    }
}
