use crate::daemon::request::{
    handle_dialog_request, FileMessageEffects, MessageEffects, TodoOperationError,
};
use crate::message::Message;
use crate::socket::DialogFilter;
use chrono::NaiveDate;
use std::collections::BTreeMap;

fn msg(from: &str, body: &str, day: u32, hour: u32) -> Message {
    Message {
        from: from.to_string(),
        subject: "S".to_string(),
        body: body.to_string(),
        timestamp: NaiveDate::from_ymd_opt(2026, 4, day)
            .unwrap()
            .and_hms_opt(hour, 0, 0)
            .unwrap(),
        metadata: BTreeMap::new(),
        is_question: false,
    }
}

#[derive(Default)]
struct FakeMessages {
    pending: Vec<(String, Message)>,
    archived_inbox: Vec<(String, Message)>,
    pending_outbox: Vec<(String, Message)>,
    archived_outbox: Vec<(String, Message)>,
}

impl MessageEffects for FakeMessages {
    fn claim_inbox_batch(
        &mut self,
    ) -> std::result::Result<Vec<(String, Message)>, TodoOperationError> {
        let claimed = std::mem::take(&mut self.pending);
        for (name, message) in &claimed {
            self.archived_inbox.push((name.clone(), message.clone()));
        }
        Ok(claimed)
    }

    fn read_inbox_archive(
        &self,
    ) -> std::result::Result<Vec<(String, Message)>, TodoOperationError> {
        Ok(self.archived_inbox.clone())
    }

    fn read_outbox(&self) -> std::result::Result<Vec<(String, Message)>, TodoOperationError> {
        Ok(self.pending_outbox.clone())
    }

    fn read_outbox_archive(
        &self,
    ) -> std::result::Result<Vec<(String, Message)>, TodoOperationError> {
        Ok(self.archived_outbox.clone())
    }
}

#[test]
fn dialog_with_pending_claims_and_marks_new() {
    let mut fake = FakeMessages {
        pending: vec![("2026-04-25T09-30-h.md".into(), msg("human", "fresh", 25, 9))],
        archived_inbox: vec![("2026-04-24T18-00-h.md".into(), msg("human", "old", 24, 18))],
        pending_outbox: vec![],
        archived_outbox: vec![],
    };
    let out = handle_dialog_request(DialogFilter::All, &[], &mut fake);
    assert!(out.ok);
    assert_eq!(
        out.claimed_filenames,
        vec!["2026-04-25T09-30-h.md".to_string()]
    );
    assert!(out.message.contains("old"));
    assert!(out.message.contains("fresh"));
    assert!(out.message.contains("new since last session"));
    assert_eq!(fake.pending.len(), 0);
    assert_eq!(fake.archived_inbox.len(), 2);
}

#[test]
fn dialog_with_no_pending_no_marker() {
    let mut fake = FakeMessages {
        pending: vec![],
        archived_inbox: vec![("2026-04-24T18-00-h.md".into(), msg("human", "old", 24, 18))],
        pending_outbox: vec![],
        archived_outbox: vec![("2026-04-24T18-05-a.md".into(), msg("agent", "hi", 24, 18))],
    };
    let out = handle_dialog_request(DialogFilter::All, &[], &mut fake);
    assert!(out.ok);
    assert!(out.claimed_filenames.is_empty());
    assert!(!out.message.contains("new since last session"));
}

#[test]
fn dialog_uses_extra_session_new_when_no_pending() {
    let mut fake = FakeMessages {
        pending: vec![],
        archived_inbox: vec![
            ("2026-04-24T18-00-h.md".into(), msg("human", "old", 24, 18)),
            (
                "2026-04-25T09-30-h.md".into(),
                msg("human", "received-earlier", 25, 9),
            ),
        ],
        pending_outbox: vec![],
        archived_outbox: vec![],
    };
    let out = handle_dialog_request(
        DialogFilter::All,
        &["2026-04-25T09-30-h.md".to_string()],
        &mut fake,
    );
    assert!(out.ok);
    assert!(out.message.contains("new since last session"));
    assert!(out.message.contains("received-earlier"));
}

#[test]
fn dialog_includes_pending_outbox_messages() {
    let dir = tempfile::tempdir().unwrap();
    let store = crate::channel::store::MessageStore::new(dir.path().to_path_buf());
    store.ensure_dirs().unwrap();
    store
        .send_out(&msg("agent", "pending reply", 25, 9))
        .unwrap();

    let mut effects = FileMessageEffects::new(dir.path());
    let out = handle_dialog_request(DialogFilter::All, &[], &mut effects);

    assert!(out.ok);
    assert!(
        out.message.contains("pending reply"),
        "pending outbox messages should be part of dialog history: {:?}",
        out.message
    );
}

#[test]
fn dialog_last_zero_rejected() {
    let mut fake = FakeMessages::default();
    let out = handle_dialog_request(DialogFilter::LastN { count: 0 }, &[], &mut fake);
    assert!(!out.ok);
    assert!(out.message.contains("--last must be at least 1"));
}

#[test]
fn dialog_since_unparseable_rejected() {
    let mut fake = FakeMessages::default();
    let out = handle_dialog_request(
        DialogFilter::Since {
            iso: "yesterday".to_string(),
        },
        &[],
        &mut fake,
    );
    assert!(!out.ok);
    assert!(out.message.contains("not a recognized timestamp"));
}

#[test]
fn dialog_since_iso_filters() {
    let mut fake = FakeMessages {
        pending: vec![],
        archived_inbox: vec![
            ("a.md".into(), msg("human", "old", 24, 18)),
            ("b.md".into(), msg("human", "fresh", 25, 9)),
        ],
        pending_outbox: vec![],
        archived_outbox: vec![],
    };
    let out = handle_dialog_request(
        DialogFilter::Since {
            iso: "2026-04-25".to_string(),
        },
        &[],
        &mut fake,
    );
    assert!(out.ok);
    assert!(!out.message.contains("old"));
    assert!(out.message.contains("fresh"));
}
