use mkfs_xfs::image::{
    diff_manifest, parse_manifest, summarize_reader_extents, ManifestDiff, ManifestParseError,
    MetadataExtentSummary, MetadataImage,
};
use mkfs_xfs::{FormatOptions, FormatPlan};
use std::io::{Cursor, ErrorKind};

const UUID: [u8; 16] = [
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
];

#[test]
fn default_1g_manifest_matches_fixture() {
    let expected = include_str!("fixtures/default_1g_manifest.csv");
    let plan = FormatPlan::new(FormatOptions::new(1 << 30, UUID)).unwrap();
    let image = MetadataImage::from_plan(&plan).unwrap();

    assert_eq!(image.manifest(), expected);
}

#[test]
fn default_1g_manifest_fixture_round_trips_to_summaries() {
    let expected = include_str!("fixtures/default_1g_manifest.csv");
    let plan = FormatPlan::new(FormatOptions::new(1 << 30, UUID)).unwrap();
    let image = MetadataImage::from_plan(&plan).unwrap();

    assert_eq!(parse_manifest(expected).unwrap(), image.extent_summaries());
    assert_eq!(
        MetadataImage::parse_manifest(expected).unwrap(),
        image.extent_summaries()
    );
}

#[test]
fn manifest_parser_rejects_malformed_rows() {
    let err = parse_manifest("offset,len,crc32c,magic\n0,512,nothex,XFSB\n").unwrap_err();

    assert_eq!(
        err,
        ManifestParseError::InvalidInteger {
            line: 2,
            field: "crc32c",
            value: "nothex".to_string(),
        }
    );
}

#[test]
fn manifest_parser_accepts_escaped_magic_bytes() {
    let parsed = parse_manifest("offset,len,crc32c,magic\n0,1,00000000,A\\x00B\\xff\n").unwrap();

    assert_eq!(parsed[0].magic, [b'A', 0, b'B', 0xff]);
}

#[test]
fn manifest_parser_rejects_huge_extents() {
    let err = parse_manifest("offset,len,crc32c,magic\n0,5368709120,00000000,XFSB\n").unwrap_err();

    assert_eq!(
        err,
        ManifestParseError::ExtentTooLarge {
            line: 2,
            len: 5_368_709_120,
            max: 4_294_967_296,
        }
    );
}

#[test]
fn manifest_diff_reports_missing_extra_and_mismatch_by_offset() {
    let expected = [
        MetadataExtentSummary {
            offset: 0,
            len: 512,
            crc32c: 1,
            magic: *b"XFSB",
        },
        MetadataExtentSummary {
            offset: 4096,
            len: 4096,
            crc32c: 2,
            magic: *b"AB3B",
        },
    ];
    let actual = [
        MetadataExtentSummary {
            offset: 0,
            len: 512,
            crc32c: 9,
            magic: *b"XFSB",
        },
        MetadataExtentSummary {
            offset: 8192,
            len: 4096,
            crc32c: 3,
            magic: *b"AB3C",
        },
    ];

    assert_eq!(
        diff_manifest(&expected, &actual),
        vec![
            ManifestDiff::Mismatch {
                expected: expected[0],
                actual: actual[0],
            },
            ManifestDiff::Missing {
                expected: expected[1],
            },
            ManifestDiff::Extra { actual: actual[1] },
        ]
    );
}

#[test]
fn metadata_image_manifest_diff_is_empty_for_fixture() {
    let expected = include_str!("fixtures/default_1g_manifest.csv");
    let plan = FormatPlan::new(FormatOptions::new(1 << 30, UUID)).unwrap();
    let image = MetadataImage::from_plan(&plan).unwrap();
    let parsed = parse_manifest(expected).unwrap();

    assert!(image.diff_manifest(&parsed).is_empty());
}

#[test]
fn reader_extent_summaries_match_written_image() {
    let plan = FormatPlan::new(FormatOptions::new(1 << 30, UUID)).unwrap();
    let image = MetadataImage::from_plan(&plan).unwrap();
    let expected = image.extent_summaries();
    let mut cursor = Cursor::new(Vec::new());

    image.write_to(&mut cursor).unwrap();
    assert_eq!(
        summarize_reader_extents(&mut cursor, &expected).unwrap(),
        expected
    );

    cursor.set_position(0);
    assert_eq!(
        image.summarize_reader_extents(&mut cursor).unwrap(),
        expected
    );
}

#[test]
fn reader_extent_summary_reports_short_extent() {
    let mut cursor = Cursor::new(vec![0_u8; 8]);
    let layout = [MetadataExtentSummary {
        offset: 4,
        len: 8,
        crc32c: 0,
        magic: [0; 4],
    }];

    let err = summarize_reader_extents(&mut cursor, &layout).unwrap_err();

    assert_eq!(err.kind(), ErrorKind::UnexpectedEof);
}

#[test]
fn reader_extent_summary_rejects_huge_extents_before_reading() {
    let mut cursor = Cursor::new(Vec::<u8>::new());
    let layout = [MetadataExtentSummary {
        offset: 0,
        len: 5_368_709_120,
        crc32c: 0,
        magic: [0; 4],
    }];

    let err = summarize_reader_extents(&mut cursor, &layout).unwrap_err();

    assert_eq!(err.kind(), ErrorKind::InvalidInput);
}
