use robson_core::conversation::{ConversationRole, Model as Conversation};
use robson_core::gateway::Model as Gateway;
use robson_core::plugin::ProcessEventKind;
use robson_core::process_event::Model as ProcessEvent;
use robson_plugin_implementation_workflow::config::WorkflowState;
use robson_plugin_implementation_workflow::orchestrator::emit_process_event;

async fn setup_agent_db() -> sea_orm::DatabaseConnection {
    let db = robson_core::connect(":memory:").await.unwrap();
    robson_core::run_migrations(&db).await.unwrap();
    db
}

fn make_workflow_config() -> robson_plugin_implementation_workflow::config::WorkflowConfig {
    use robson_connector_jira::JiraConfig;
    use robson_gateway_slack::SlackAgentConfig;
    use robson_plugin_implementation_workflow::config::{
        ContainerConfig, OrchestratorConfig, WorkflowConfig, WorkflowDbConfig,
    };

    WorkflowConfig {
        orchestrator: OrchestratorConfig {
            concurrency: 1,
            db_path: ":memory:".into(),
            webhook_port: 3000,
            claude_bin: None,
            api_token: None,
            ssh_key_path: None,
            git_name: "Test".into(),
            git_email: "test@example.com".into(),
            parallel_workers: false,
            tmp_dir: ".tmp".into(),
        },
        workflow: WorkflowDbConfig::default(),
        jira: JiraConfig {
            base_url: "http://jira.example.com".into(),
            email: "test@example.com".into(),
            token: "token".into(),
            filter_id: String::new(),
        },
        bitbucket: None,
        slack_agent: SlackAgentConfig::default(),
        container: ContainerConfig::default(),
    }
}

// ── emit_process_event: agent context present ─────────────────────────────────

#[tokio::test]
async fn test_emit_process_event_inserts_when_agent_context_set() {
    let agent_db = setup_agent_db().await;
    let slack = Gateway::find_by_name(&agent_db, "slack")
        .await
        .unwrap()
        .unwrap();

    // Create a conversation so we have a valid conversation_id
    let conv = Conversation::insert(
        &agent_db,
        Some(slack.id),
        "C_test",
        "",
        "U1",
        ConversationRole::User,
        "run task",
    )
    .await
    .unwrap();

    let state = WorkflowState::new_with_agent_context(
        // workflow_db is unused in this test; reuse agent_db for simplicity
        agent_db.clone(),
        make_workflow_config(),
        agent_db.clone(),
        conv.id,
    );

    emit_process_event(&state, ProcessEventKind::Started, "Task ABC-1 started").await;

    let events = ProcessEvent::find_by_conversation(&agent_db, conv.id)
        .await
        .unwrap();
    assert_eq!(events.len(), 1);
    assert_eq!(events[0].kind, "started");
    assert_eq!(events[0].content, "Task ABC-1 started");
}

#[tokio::test]
async fn test_emit_process_event_uses_correct_conversation_id() {
    let agent_db = setup_agent_db().await;
    let slack = Gateway::find_by_name(&agent_db, "slack")
        .await
        .unwrap()
        .unwrap();

    let conv1 = Conversation::insert(
        &agent_db,
        Some(slack.id),
        "C1",
        "",
        "U1",
        ConversationRole::User,
        "task 1",
    )
    .await
    .unwrap();
    let conv2 = Conversation::insert(
        &agent_db,
        Some(slack.id),
        "C2",
        "",
        "U2",
        ConversationRole::User,
        "task 2",
    )
    .await
    .unwrap();

    let state = WorkflowState::new_with_agent_context(
        agent_db.clone(),
        make_workflow_config(),
        agent_db.clone(),
        conv1.id,
    );

    emit_process_event(&state, ProcessEventKind::Progress, "doing work").await;

    let events1 = ProcessEvent::find_by_conversation(&agent_db, conv1.id)
        .await
        .unwrap();
    let events2 = ProcessEvent::find_by_conversation(&agent_db, conv2.id)
        .await
        .unwrap();

    assert_eq!(events1.len(), 1, "event must go to conv1 only");
    assert!(events2.is_empty(), "conv2 must not receive the event");
}

// ── emit_process_event: no agent context (CLI path) ──────────────────────────

#[tokio::test]
async fn test_emit_process_event_is_noop_without_agent_db() {
    let agent_db = setup_agent_db().await;
    let slack = Gateway::find_by_name(&agent_db, "slack")
        .await
        .unwrap()
        .unwrap();

    let conv = Conversation::insert(
        &agent_db,
        Some(slack.id),
        "C1",
        "",
        "U1",
        ConversationRole::User,
        "msg",
    )
    .await
    .unwrap();

    // CLI state: no agent_db, no conversation_id
    let cli_state = WorkflowState::new(agent_db.clone(), make_workflow_config());

    emit_process_event(&cli_state, ProcessEventKind::Started, "should not appear").await;

    let events = ProcessEvent::find_by_conversation(&agent_db, conv.id)
        .await
        .unwrap();
    assert!(
        events.is_empty(),
        "CLI path must not emit any process events"
    );
}

#[tokio::test]
async fn test_emit_process_event_is_noop_without_conversation_id() {
    let agent_db = setup_agent_db().await;

    // Emit is a no-op when conversation_id is None (no panic, no DB rows).
    let state = std::sync::Arc::new(
        robson_plugin_implementation_workflow::config::WorkflowState {
            workflow_db: agent_db.clone(),
            config: make_workflow_config(),
            agent_db: Some(agent_db.clone()),
            conversation_id: None,
        },
    );

    // Should not panic and should not insert anything
    emit_process_event(&state, ProcessEventKind::Progress, "ignored").await;

    let events = ProcessEvent::find_undelivered(&agent_db).await.unwrap();
    assert!(events.is_empty());
}

// ── all ProcessEventKind variants round-trip through the DB ──────────────────

#[tokio::test]
async fn test_emit_all_process_event_kinds() {
    let agent_db = setup_agent_db().await;
    let slack = Gateway::find_by_name(&agent_db, "slack")
        .await
        .unwrap()
        .unwrap();

    let conv = Conversation::insert(
        &agent_db,
        Some(slack.id),
        "C1",
        "",
        "U1",
        ConversationRole::User,
        "msg",
    )
    .await
    .unwrap();

    let state = WorkflowState::new_with_agent_context(
        agent_db.clone(),
        make_workflow_config(),
        agent_db.clone(),
        conv.id,
    );

    emit_process_event(&state, ProcessEventKind::Started, "started").await;
    emit_process_event(&state, ProcessEventKind::Progress, "progress").await;
    emit_process_event(&state, ProcessEventKind::Completed, "completed").await;
    emit_process_event(&state, ProcessEventKind::Failed, "failed").await;

    let events = ProcessEvent::find_by_conversation(&agent_db, conv.id)
        .await
        .unwrap();
    assert_eq!(events.len(), 4);
    let kinds: Vec<&str> = events.iter().map(|e| e.kind.as_str()).collect();
    assert_eq!(kinds, vec!["started", "progress", "completed", "failed"]);
}
