use std::collections::HashMap;
use std::path::PathBuf;

use chrono::{TimeZone, Utc};
use cueward_core::{AttachmentKind, Cue, CueSource};
use crate::notes::db::test_support::with_temp_home;

use super::{
    AttachmentOcrBlock, MediaAttachment, MediaNote, append_attachment_ocr,
    attachment_placeholder_count, build_image_segments, enrich_cues_with_media,
    replace_attachment_labels,
};

#[test]
fn replace_attachment_labels_uses_filenames() {
    let body = "[Attachment]\n[Attachment]";
    let attachments = vec![
        MediaAttachment {
            filename: "one.png".into(),
            path: PathBuf::from("/tmp/one.png"),
            sha256: None,
        },
        MediaAttachment {
            filename: "two.jpg".into(),
            path: PathBuf::from("/tmp/two.jpg"),
            sha256: None,
        },
    ];

    let replaced = replace_attachment_labels(
        body,
        &attachments
            .iter()
            .map(|attachment| attachment.filename.clone())
            .collect::<Vec<_>>(),
        2,
    );

    assert_eq!(replaced, "[Attachment 1: one.png]\n[Attachment 2: two.jpg]");
}

#[test]
fn enrich_cues_with_media_adds_paths_and_filenames() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 9, 23, 42, 54).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("新增備忘錄".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let media_notes = vec![MediaNote {
        timestamp: timestamp.timestamp() - 1,
        title: None,
        attachments: vec![MediaAttachment {
            filename: "scan.jpg".into(),
            path: PathBuf::from("/tmp/scan.jpg"),
            sha256: Some("abc123".into()),
        }],
    }];

    enrich_cues_with_media(&mut cues, &media_notes);

    assert_eq!(cues[0].content, "[Attachment 1: scan.jpg]");
    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Image
    ));
    assert_eq!(
        cues[0].attachment_segments[0].filename.as_deref(),
        Some("scan.jpg")
    );
    assert_eq!(
        cues[0].attachment_segments[0].path.as_deref(),
        Some("/tmp/scan.jpg")
    );
    assert_eq!(
        cues[0].attachment_segments[0].sha256.as_deref(),
        Some("abc123")
    );
    assert!(!cues[0].attachment_segments[0].has_ocr);
}

#[test]
fn append_attachment_ocr_adds_readable_sections() {
    let content = "[Attachment 1: image.png]";
    let blocks = vec![AttachmentOcrBlock {
        index: 1,
        filename: "image.png".into(),
        sha256: Some("hash1".into()),
        text: "detected text".into(),
    }];

    let combined = append_attachment_ocr(content, &blocks);

    assert_eq!(
        combined,
        "[Attachment 1: image.png]\n\n[Attachment 1 OCR: image.png]\ndetected text"
    );
}

#[test]
fn attachment_placeholder_count_counts_remaining_plain_labels() {
    assert_eq!(attachment_placeholder_count("[Attachment]\n[Attachment]"), 2);
    assert_eq!(attachment_placeholder_count("[Attachment 1: image.png]"), 0);
}

#[test]
fn build_attachment_segments_includes_ocr_fields() {
    let attachments = vec![MediaAttachment {
        filename: "image.png".into(),
        path: PathBuf::from("/tmp/image.png"),
        sha256: Some("hash1".into()),
    }];
    let blocks = vec![AttachmentOcrBlock {
        index: 1,
        filename: "image.png".into(),
        sha256: Some("hash1".into()),
        text: "detected text".into(),
    }];

    let segments = build_image_segments(&attachments, Some(&blocks));

    assert_eq!(segments.len(), 1);
    assert_eq!(segments[0].index, 1);
    assert!(matches!(segments[0].kind, AttachmentKind::Image));
    assert_eq!(segments[0].filename.as_deref(), Some("image.png"));
    assert_eq!(segments[0].path.as_deref(), Some("/tmp/image.png"));
    assert_eq!(segments[0].sha256.as_deref(), Some("hash1"));
    assert_eq!(segments[0].ocr_text.as_deref(), Some("detected text"));
    assert!(segments[0].has_ocr);
}

#[test]
fn build_attachment_segments_prefers_sha256_over_filename() {
    let attachments = vec![
        MediaAttachment {
            filename: "image.png".into(),
            path: PathBuf::from("/tmp/one.png"),
            sha256: Some("hash-a".into()),
        },
        MediaAttachment {
            filename: "image.png".into(),
            path: PathBuf::from("/tmp/two.png"),
            sha256: Some("hash-b".into()),
        },
    ];
    let blocks = vec![
        AttachmentOcrBlock {
            index: 1,
            filename: "image.png".into(),
            sha256: Some("hash-a".into()),
            text: "first".into(),
        },
        AttachmentOcrBlock {
            index: 2,
            filename: "image.png".into(),
            sha256: Some("hash-b".into()),
            text: "second".into(),
        },
    ];

    let segments = build_image_segments(&attachments, Some(&blocks));

    assert_eq!(segments[0].ocr_text.as_deref(), Some("first"));
    assert_eq!(segments[1].ocr_text.as_deref(), Some("second"));
}

#[test]
fn append_attachment_ocr_keeps_original_attachment_indices() {
    let content = "[Attachment 1: one.png]\n[Attachment 2: two.png]";
    let blocks = vec![AttachmentOcrBlock {
        index: 2,
        filename: "two.png".into(),
        sha256: Some("hash2".into()),
        text: "detected text".into(),
    }];

    let combined = append_attachment_ocr(content, &blocks);

    assert!(combined.contains("[Attachment 2 OCR: two.png]"));
}

#[test]
fn enrich_cues_with_attachments_emits_unresolved_when_no_media_matches() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 10, 8, 0, 0).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("Unresolved".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];

    super::enrich_cues_with_attachments(&mut cues, &[], &[], &[], &[], &[], &[]);

    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Unresolved
    ));
    assert_eq!(cues[0].attachment_segments[0].filename, None);
    assert_eq!(cues[0].attachment_segments[0].path, None);
    assert!(!cues[0].attachment_segments[0].has_ocr);
}

#[test]
fn enrich_cues_with_attachments_emits_unresolved_when_match_has_no_attachments() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 10, 8, 0, 0).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]\n[Attachment]".into(),
        url: None,
        title: Some("Still unresolved".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let media_notes = vec![MediaNote {
        timestamp: timestamp.timestamp(),
        title: Some("Still unresolved".into()),
        attachments: Vec::new(),
    }];

    super::enrich_cues_with_attachments(&mut cues, &media_notes, &[], &[], &[], &[], &[]);

    assert_eq!(cues[0].attachment_segments.len(), 2);
    assert!(cues[0]
        .attachment_segments
        .iter()
        .all(|segment| matches!(segment.kind, AttachmentKind::Unresolved)));
    assert!(cues[0]
        .attachment_segments
        .iter()
        .all(|segment| segment.path.is_none() && segment.filename.is_none()));
}

#[test]
fn enrich_cues_with_attachments_combines_media_and_web_preview_segments() {
    with_temp_home(|_| {
        let timestamp = Utc.with_ymd_and_hms(2026, 4, 10, 9, 0, 0).unwrap();
        let mut cues = vec![Cue {
            source: CueSource::Notes,
            timestamp,
            content: "[Attachment]\n[Attachment]".into(),
            url: None,
            title: Some("Mixed note".into()),
            tags: Vec::new(),
            attachment_segments: Vec::new(),
            metadata: HashMap::new(),
        }];
        let media_notes = vec![MediaNote {
            timestamp: timestamp.timestamp(),
            title: Some("Mixed note".into()),
            attachments: vec![MediaAttachment {
                filename: "scan.jpg".into(),
                path: PathBuf::from("/tmp/scan.jpg"),
                sha256: Some("abc123".into()),
            }],
        }];
        let web_preview_notes = vec![crate::notes::WebPreviewNote {
            timestamp: timestamp.timestamp(),
            title: Some("Mixed note".into()),
            attachments: vec![crate::notes::WebPreviewAttachment {
                title: Some("Cursor Docs".into()),
                url: "https://docs.cursor.com/guides/working-with-context".into(),
            }],
        }];

        super::enrich_cues_with_attachments(
            &mut cues,
            &media_notes,
            &web_preview_notes,
            &[],
            &[],
            &[],
            &[],
        );

        assert_eq!(
            cues[0].content,
            "[Attachment 1: scan.jpg]\n[Attachment 2: Cursor Docs]"
        );
        assert_eq!(cues[0].attachment_segments.len(), 2);
        assert_eq!(cues[0].attachment_segments[0].index, 1);
        assert_eq!(cues[0].attachment_segments[1].index, 2);
        assert!(matches!(
            cues[0].attachment_segments[0].kind,
            AttachmentKind::Image
        ));
        assert!(matches!(
            cues[0].attachment_segments[1].kind,
            AttachmentKind::WebPreview
        ));
    });
}

#[test]
fn enrich_cues_with_map_emits_structured_map_segment() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 13, 16, 24, 54).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("新增備忘錄".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let map_notes = vec![crate::notes::MapNote {
        timestamp: timestamp.timestamp(),
        title: Some("新增備忘錄".into()),
        attachments: vec![crate::notes::MapAttachment {
            title: Some("屏東縣立棒球場".into()),
            url: Some("https://maps.apple.com/place?...".into()),
            latitude: 22.657349,
            longitude: 120.485956,
        }],
    }];

    super::enrich_cues_with_attachments(&mut cues, &[], &[], &map_notes, &[], &[], &[]);

    assert_eq!(cues[0].content, "[Attachment 1: 屏東縣立棒球場]");
    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Map
    ));
    assert_eq!(
        cues[0].attachment_segments[0].title.as_deref(),
        Some("屏東縣立棒球場")
    );
    assert_eq!(
        cues[0].attachment_segments[0].url.as_deref(),
        Some("https://maps.apple.com/place?...")
    );
    assert_eq!(cues[0].attachment_segments[0].latitude, Some(22.657349));
    assert_eq!(cues[0].attachment_segments[0].longitude, Some(120.485956));
    assert!(!cues[0].attachment_segments[0].has_ocr);
}

#[test]
fn enrich_cues_with_attachments_combines_media_and_map_segments() {
    with_temp_home(|_| {
        let timestamp = Utc.with_ymd_and_hms(2026, 4, 13, 16, 24, 54).unwrap();
        let mut cues = vec![Cue {
            source: CueSource::Notes,
            timestamp,
            content: "[Attachment]\n[Attachment]".into(),
            url: None,
            title: Some("新增備忘錄".into()),
            tags: Vec::new(),
            attachment_segments: Vec::new(),
            metadata: HashMap::new(),
        }];
        let media_notes = vec![MediaNote {
            timestamp: timestamp.timestamp(),
            title: Some("新增備忘錄".into()),
            attachments: vec![MediaAttachment {
                filename: "scan.jpg".into(),
                path: PathBuf::from("/tmp/scan.jpg"),
                sha256: Some("abc123".into()),
            }],
        }];
        let map_notes = vec![crate::notes::MapNote {
            timestamp: timestamp.timestamp(),
            title: Some("新增備忘錄".into()),
            attachments: vec![crate::notes::MapAttachment {
                title: Some("屏東縣立棒球場".into()),
                url: Some("https://maps.apple.com/place?...".into()),
                latitude: 22.657349,
                longitude: 120.485956,
            }],
        }];

        super::enrich_cues_with_attachments(&mut cues, &media_notes, &[], &map_notes, &[], &[], &[]);

        assert_eq!(
            cues[0].content,
            "[Attachment 1: scan.jpg]\n[Attachment 2: 屏東縣立棒球場]"
        );
        assert_eq!(cues[0].attachment_segments.len(), 2);
        assert_eq!(cues[0].attachment_segments[0].index, 1);
        assert_eq!(cues[0].attachment_segments[1].index, 2);
        assert!(matches!(
            cues[0].attachment_segments[0].kind,
            AttachmentKind::Image
        ));
        assert!(matches!(
            cues[0].attachment_segments[1].kind,
            AttachmentKind::Map
        ));
    });
}

#[test]
fn enrich_cues_with_pdf_emits_file_backed_pdf_segment() {
    with_temp_home(|_| {
        let timestamp = Utc.with_ymd_and_hms(2026, 4, 14, 0, 10, 15).unwrap();
        let mut cues = vec![Cue {
            source: CueSource::Notes,
            timestamp,
            content: "[Attachment]".into(),
            url: None,
            title: Some("test".into()),
            tags: Vec::new(),
            attachment_segments: Vec::new(),
            metadata: HashMap::new(),
        }];
        let file_backed_notes = vec![crate::notes::FileBackedNote {
            timestamp: timestamp.timestamp(),
            title: Some("test".into()),
            attachments: vec![crate::notes::FileBackedAttachment {
                kind: AttachmentKind::Pdf,
                title: Some("SK-INFLUX [V MB]_DS_C0919.pdf".into()),
                filename: "SK-INFLUX [V MB]_DS_C0919.pdf".into(),
                path: Some(PathBuf::from("/tmp/SK-INFLUX [V MB]_DS_C0919.pdf")),
                sha256: Some("abc123".into()),
                page_count: Some(4),
            }],
        }];

        super::enrich_cues_with_attachments(&mut cues, &[], &[], &[], &file_backed_notes, &[], &[]);

        assert_eq!(cues[0].content, "[Attachment 1: SK-INFLUX [V MB]_DS_C0919.pdf]");
        assert_eq!(cues[0].attachment_segments.len(), 1);
        assert!(matches!(
            cues[0].attachment_segments[0].kind,
            AttachmentKind::Pdf
        ));
        assert_eq!(
            cues[0].attachment_segments[0].path.as_deref(),
            Some("/tmp/SK-INFLUX [V MB]_DS_C0919.pdf")
        );
        assert_eq!(
            cues[0].attachment_segments[0].sha256.as_deref(),
            Some("abc123")
        );
        assert_eq!(cues[0].attachment_segments[0].page_count, Some(4));
        assert!(!cues[0].attachment_segments[0].has_ocr);
    });
}

#[test]
fn enrich_cues_with_binary_emits_file_backed_binary_segment() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 9, 23, 42, 53).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("新增備忘錄".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let file_backed_notes = vec![crate::notes::FileBackedNote {
        timestamp: timestamp.timestamp(),
        title: Some("新增備忘錄".into()),
        attachments: vec![crate::notes::FileBackedAttachment {
            kind: AttachmentKind::Binary,
            title: None,
            filename: "34BF703C-DD8C-4607-AA9C-2A71623C2884".into(),
            path: Some(PathBuf::from("/tmp/34BF703C-DD8C-4607-AA9C-2A71623C2884")),
            sha256: Some("def456".into()),
            page_count: None,
        }],
    }];

    super::enrich_cues_with_attachments(&mut cues, &[], &[], &[], &file_backed_notes, &[], &[]);

    assert_eq!(
        cues[0].content,
        "[Attachment 1: 34BF703C-DD8C-4607-AA9C-2A71623C2884]"
    );
    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Binary
    ));
    assert_eq!(
        cues[0].attachment_segments[0].path.as_deref(),
        Some("/tmp/34BF703C-DD8C-4607-AA9C-2A71623C2884")
    );
    assert_eq!(
        cues[0].attachment_segments[0].sha256.as_deref(),
        Some("def456")
    );
    assert!(!cues[0].attachment_segments[0].has_ocr);
}

#[test]
fn enrich_cues_with_binary_without_materialized_path_keeps_typed_segment() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 9, 23, 42, 53).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("新增備忘錄".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let file_backed_notes = vec![crate::notes::FileBackedNote {
        timestamp: timestamp.timestamp(),
        title: Some("新增備忘錄".into()),
        attachments: vec![crate::notes::FileBackedAttachment {
            kind: AttachmentKind::Binary,
            title: None,
            filename: "missing.bin".into(),
            path: None,
            sha256: None,
            page_count: None,
        }],
    }];

    super::enrich_cues_with_attachments(&mut cues, &[], &[], &[], &file_backed_notes, &[], &[]);

    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Binary
    ));
    assert_eq!(cues[0].attachment_segments[0].path, None);
    assert_eq!(
        cues[0].attachment_segments[0].filename.as_deref(),
        Some("missing.bin")
    );
}

#[test]
fn enrich_cues_with_drawing_emits_typed_drawing_segment() {
    let timestamp = Utc.with_ymd_and_hms(2026, 4, 16, 10, 0, 0).unwrap();
    let mut cues = vec![Cue {
        source: CueSource::Notes,
        timestamp,
        content: "[Attachment]".into(),
        url: None,
        title: Some("畫圖測試".into()),
        tags: Vec::new(),
        attachment_segments: Vec::new(),
        metadata: HashMap::new(),
    }];
    let drawing_notes = vec![crate::notes::DrawingNote {
        timestamp: timestamp.timestamp(),
        title: Some("畫圖測試".into()),
        attachments: vec![crate::notes::DrawingAttachment {
            title: Some("Inline Sketch".into()),
        }],
    }];

    super::enrich_cues_with_attachments(
        &mut cues,
        &[],
        &[],
        &[],
        &[],
        &[],
        &drawing_notes,
    );

    assert_eq!(cues[0].attachment_segments.len(), 1);
    assert!(matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Drawing
    ));
    assert!(!matches!(
        cues[0].attachment_segments[0].kind,
        AttachmentKind::Unresolved
    ));
}
