use super::{Integration, IntegrationConfig, IntegrationStore};
use super::webhook::{ReleasePayload, WebhookPayload};

/// A notification event from Forest, normalized for routing.
#[derive(Debug, Clone)]
pub struct NotificationEvent {
    pub id: String,
    pub notification_type: String,
    pub title: String,
    pub body: String,
    pub organisation: String,
    pub project: String,
    pub timestamp: String,
    pub release: Option<ReleaseContext>,
}

/// Release context from the notification event.
#[derive(Debug, Clone)]
pub struct ReleaseContext {
    pub slug: String,
    pub artifact_id: String,
    pub destination: String,
    pub environment: String,
    pub source_username: String,
    pub commit_sha: String,
    pub commit_branch: String,
    pub error_message: Option<String>,
}

/// A dispatch task produced by the router: what to send where.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum DispatchTask {
    Webhook {
        integration_id: String,
        url: String,
        secret: Option<String>,
        headers: std::collections::HashMap<String, String>,
        payload: WebhookPayload,
    },
    Slack {
        integration_id: String,
        webhook_url: String,
        message: SlackMessage,
    },
}

/// A formatted Slack message (Block Kit compatible).
#[derive(Debug, Clone, serde::Serialize)]
pub struct SlackMessage {
    pub text: String,
    pub color: String,
    pub blocks: Vec<serde_json::Value>,
}

/// Route a notification event to dispatch tasks based on matching integrations.
pub fn route_notification(
    event: &NotificationEvent,
    integrations: &[Integration],
) -> Vec<DispatchTask> {
    let payload = build_webhook_payload(event);

    integrations
        .iter()
        .map(|integration| match &integration.config {
            IntegrationConfig::Webhook {
                url,
                secret,
                headers,
            } => DispatchTask::Webhook {
                integration_id: integration.id.clone(),
                url: url.clone(),
                secret: secret.clone(),
                headers: headers.clone(),
                payload: payload.clone(),
            },
            IntegrationConfig::Slack { webhook_url, .. } => {
                let message = format_slack_message(event);
                DispatchTask::Slack {
                    integration_id: integration.id.clone(),
                    webhook_url: webhook_url.clone(),
                    message,
                }
            }
        })
        .collect()
}

/// Find matching integrations and produce dispatch tasks.
pub async fn route_notification_for_org(
    store: &dyn IntegrationStore,
    event: &NotificationEvent,
) -> Vec<DispatchTask> {
    match store
        .list_matching_integrations(&event.organisation, &event.notification_type)
        .await
    {
        Ok(integrations) => route_notification(event, &integrations),
        Err(e) => {
            tracing::error!(org = %event.organisation, error = %e, "failed to list matching integrations");
            vec![]
        }
    }
}

fn build_webhook_payload(event: &NotificationEvent) -> WebhookPayload {
    WebhookPayload {
        event: event.notification_type.clone(),
        timestamp: event.timestamp.clone(),
        organisation: event.organisation.clone(),
        project: event.project.clone(),
        notification_id: event.id.clone(),
        title: event.title.clone(),
        body: event.body.clone(),
        release: event.release.as_ref().map(|r| ReleasePayload {
            slug: r.slug.clone(),
            artifact_id: r.artifact_id.clone(),
            destination: r.destination.clone(),
            environment: r.environment.clone(),
            source_username: r.source_username.clone(),
            commit_sha: r.commit_sha.clone(),
            commit_branch: r.commit_branch.clone(),
            error_message: r.error_message.clone(),
        }),
    }
}

fn format_slack_message(event: &NotificationEvent) -> SlackMessage {
    let color = match event.notification_type.as_str() {
        "release_succeeded" => "#36a64f",
        "release_failed" => "#dc3545",
        "release_started" => "#0d6efd",
        "release_annotated" => "#6c757d",
        _ => "#6c757d",
    };

    let status_emoji = match event.notification_type.as_str() {
        "release_succeeded" => ":white_check_mark:",
        "release_failed" => ":x:",
        "release_started" => ":rocket:",
        "release_annotated" => ":memo:",
        _ => ":bell:",
    };

    // Fallback text (shown in notifications/previews)
    let text = format!("{} {}", status_emoji, event.title);

    // Build Block Kit blocks
    let mut blocks: Vec<serde_json::Value> = Vec::new();

    // Header
    blocks.push(serde_json::json!({
        "type": "header",
        "text": {
            "type": "plain_text",
            "text": event.title,
            "emoji": true
        }
    }));

    // Body section (if present)
    if !event.body.is_empty() {
        blocks.push(serde_json::json!({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": event.body
            }
        }));
    }

    // Release metadata fields
    if let Some(ref r) = event.release {
        let mut fields = vec![
            serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Organisation*\n{}", event.organisation)
            }),
            serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Project*\n{}", event.project)
            }),
        ];

        if !r.destination.is_empty() {
            fields.push(serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Destination*\n`{}`", r.destination)
            }));
        }

        if !r.environment.is_empty() {
            fields.push(serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Environment*\n{}", r.environment)
            }));
        }

        if !r.commit_sha.is_empty() {
            let short_sha = &r.commit_sha[..r.commit_sha.len().min(7)];
            fields.push(serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Commit*\n`{}`", short_sha)
            }));
        }

        if !r.commit_branch.is_empty() {
            fields.push(serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Branch*\n`{}`", r.commit_branch)
            }));
        }

        if !r.source_username.is_empty() {
            fields.push(serde_json::json!({
                "type": "mrkdwn",
                "text": format!("*Author*\n{}", r.source_username)
            }));
        }

        blocks.push(serde_json::json!({
            "type": "section",
            "fields": fields
        }));

        // Error message (if any)
        if let Some(ref err) = r.error_message {
            blocks.push(serde_json::json!({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": format!(":warning: *Error:* {}", err)
                }
            }));
        }
    }

    // Context line with timestamp
    blocks.push(serde_json::json!({
        "type": "context",
        "elements": [{
            "type": "mrkdwn",
            "text": format!("{} | {}", event.notification_type.replace('_', " "), event.timestamp)
        }]
    }));

    SlackMessage {
        text,
        color: color.to_string(),
        blocks,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    fn test_event() -> NotificationEvent {
        NotificationEvent {
            id: "notif-1".into(),
            notification_type: "release_failed".into(),
            title: "Release failed".into(),
            body: "Container timeout".into(),
            organisation: "test-org".into(),
            project: "my-project".into(),
            timestamp: "2026-03-09T14:30:00Z".into(),
            release: Some(ReleaseContext {
                slug: "test-release".into(),
                artifact_id: "art_123".into(),
                destination: "prod-eu".into(),
                environment: "production".into(),
                source_username: "alice".into(),
                commit_sha: "abc1234def".into(),
                commit_branch: "main".into(),
                error_message: Some("health check timeout".into()),
            }),
        }
    }

    fn webhook_integration(id: &str) -> Integration {
        Integration {
            id: id.into(),
            organisation: "test-org".into(),
            integration_type: super::super::IntegrationType::Webhook,
            name: "prod-alerts".into(),
            config: IntegrationConfig::Webhook {
                url: "https://hooks.example.com/test".into(),
                secret: Some("s3cret".into()),
                headers: HashMap::new(),
            },
            enabled: true,
            created_by: "user-1".into(),
            created_at: "2026-03-09T00:00:00Z".into(),
            updated_at: "2026-03-09T00:00:00Z".into(),
            api_token: None,
        }
    }

    fn slack_integration(id: &str) -> Integration {
        Integration {
            id: id.into(),
            organisation: "test-org".into(),
            integration_type: super::super::IntegrationType::Slack,
            name: "#deploys".into(),
            config: IntegrationConfig::Slack {
                team_id: "T123".into(),
                team_name: "Test".into(),
                channel_id: "C456".into(),
                channel_name: "#deploys".into(),
                access_token: "xoxb-test".into(),
                webhook_url: "https://hooks.slack.com/test".into(),
            },
            enabled: true,
            created_by: "user-1".into(),
            created_at: "2026-03-09T00:00:00Z".into(),
            updated_at: "2026-03-09T00:00:00Z".into(),
            api_token: None,
        }
    }

    #[test]
    fn route_to_webhook() {
        let event = test_event();
        let integrations = vec![webhook_integration("w1")];
        let tasks = route_notification(&event, &integrations);

        assert_eq!(tasks.len(), 1);
        match &tasks[0] {
            DispatchTask::Webhook {
                integration_id,
                url,
                secret,
                payload,
                ..
            } => {
                assert_eq!(integration_id, "w1");
                assert_eq!(url, "https://hooks.example.com/test");
                assert_eq!(secret.as_deref(), Some("s3cret"));
                assert_eq!(payload.event, "release_failed");
                assert_eq!(payload.organisation, "test-org");
            }
            _ => panic!("expected Webhook task"),
        }
    }

    #[test]
    fn route_to_slack() {
        let event = test_event();
        let integrations = vec![slack_integration("s1")];
        let tasks = route_notification(&event, &integrations);

        assert_eq!(tasks.len(), 1);
        match &tasks[0] {
            DispatchTask::Slack {
                integration_id,
                message,
                ..
            } => {
                assert_eq!(integration_id, "s1");
                assert!(message.text.contains("Release failed"));
                assert_eq!(message.color, "#dc3545"); // red for failure
            }
            _ => panic!("expected Slack task"),
        }
    }

    #[test]
    fn route_to_multiple_integrations() {
        let event = test_event();
        let integrations = vec![webhook_integration("w1"), slack_integration("s1")];
        let tasks = route_notification(&event, &integrations);
        assert_eq!(tasks.len(), 2);
    }

    #[test]
    fn route_to_empty_integrations() {
        let event = test_event();
        let tasks = route_notification(&event, &[]);
        assert!(tasks.is_empty());
    }

    #[test]
    fn slack_message_color_success() {
        let mut event = test_event();
        event.notification_type = "release_succeeded".into();
        let msg = format_slack_message(&event);
        assert_eq!(msg.color, "#36a64f");
    }

    #[test]
    fn slack_message_includes_error() {
        let event = test_event();
        let msg = format_slack_message(&event);
        // Error message is rendered in blocks, not the fallback text field
        let blocks_str = serde_json::to_string(&msg.blocks).unwrap();
        assert!(blocks_str.contains("health check timeout"));
    }
}
