//! Integration tests for sectioned storage

use geographdb_core::storage::sectioned::{SectionedStorage, FILE_MAGIC, HEADER_SIZE};
use std::io::{Read, Seek, SeekFrom, Write};
use tempfile::NamedTempFile;

// ============================================================================
// BASIC CREATE/OPEN TESTS
// ============================================================================

#[test]
fn test_create_creates_valid_header() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    let storage = SectionedStorage::create(path).unwrap();

    // Verify header was written correctly
    let mut buf = [0u8; 128];
    let mut file = std::fs::File::open(path).unwrap();
    file.read_exact(&mut buf).unwrap();

    assert_eq!(&buf[0..8], &FILE_MAGIC[..]);
    assert_eq!(u32::from_le_bytes(buf[8..12].try_into().unwrap()), 1);
    assert_eq!(storage.section_count(), 0);
}

#[test]
fn test_create_open_roundtrip() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    {
        let mut storage = SectionedStorage::create(path).unwrap();
        storage.create_section("data", 1000, 0).unwrap();
        storage.write_section("data", b"hello").unwrap();
        storage.flush().unwrap();
    }

    {
        let mut storage = SectionedStorage::open(path).unwrap();
        assert_eq!(storage.section_count(), 1);
        let data = storage.read_section("data").unwrap();
        assert_eq!(data, b"hello");
    }
}

#[test]
fn test_write_read_exact_bytes_no_padding() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    let mut storage = SectionedStorage::create(path).unwrap();
    storage.create_section("test", 1000, 0).unwrap();
    storage.write_section("test", b"exact").unwrap();

    let read = storage.read_section("test").unwrap();
    assert_eq!(read.len(), 5); // Exactly "exact".len()
    assert_eq!(read, b"exact");
}

#[test]
fn test_write_exceeding_capacity_fails() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    let mut storage = SectionedStorage::create(path).unwrap();
    storage.create_section("small", 10, 0).unwrap();

    let result = storage.write_section("small", &[0u8; 100]);
    assert!(result.is_err());
}

#[test]
fn test_duplicate_section_rejected() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    let mut storage = SectionedStorage::create(path).unwrap();
    storage.create_section("dup", 100, 0).unwrap();

    let result = storage.create_section("dup", 200, 0);
    assert!(result.is_err());
}

#[test]
fn test_validate_required_sections() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    let mut storage = SectionedStorage::create(path).unwrap();
    storage.create_section("required1", 100, 0).unwrap();
    storage.create_section("required2", 100, 0).unwrap();
    storage.flush().unwrap();

    // Should pass with all required
    storage
        .validate_required_sections(&["required1", "required2"])
        .unwrap();

    // Should fail with missing section
    let result = storage.validate_required_sections(&["required1", "missing"]);
    assert!(result.is_err());
}

// ============================================================================
// INVALID MAGIC TEST
// ============================================================================

#[test]
fn test_open_fails_on_invalid_magic() {
    let temp = NamedTempFile::new().unwrap();

    // Write a FULL 128-byte header with wrong magic
    let mut file = std::fs::File::create(temp.path()).unwrap();
    let mut fake_header = [0u8; 128];
    fake_header[0..8].copy_from_slice(b"BADMAGIC"); // Wrong magic
    fake_header[8..12].copy_from_slice(&1u32.to_le_bytes()); // version
    fake_header[16..24].copy_from_slice(&128u64.to_le_bytes()); // section_table_offset
    fake_header[32..40].copy_from_slice(&128u64.to_le_bytes()); // next_data_offset
    file.write_all(&fake_header).unwrap();

    let result = SectionedStorage::open(temp.path());
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(
        err.contains("magic") || err.contains("MAGIC"),
        "Error should mention invalid magic, got: {}",
        err
    );
}

// ============================================================================
// CHECKSUM TESTS
// ============================================================================

#[test]
fn test_checksum_mismatch_detected() {
    let temp = NamedTempFile::new().unwrap();
    let path = temp.path();

    // Create valid file
    {
        let mut storage = SectionedStorage::create(path).unwrap();
        storage.create_section("data", 100, 0).unwrap();
        storage.write_section("data", b"original").unwrap();
        storage.flush().unwrap();
    }

    // Corrupt the section data on disk
    {
        let mut file = std::fs::OpenOptions::new().write(true).open(path).unwrap();
        file.seek(SeekFrom::Start(HEADER_SIZE)).unwrap();
        file.write_all(b"corrupted!").unwrap();
    }

    let result = SectionedStorage::open(temp.path());
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    // Checksum error should be in the message
    assert!(
        err.contains("Checksum mismatch") || err.contains("checksum"),
        "Error should mention checksum, got: {}",
        err
    );
}

// ============================================================================
// PHYSICAL RESERVATION TESTS
// ============================================================================

#[test]
fn test_create_section_physically_reserves_space() {
    let temp = NamedTempFile::new().unwrap();

    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    // File should be exactly header size initially
    let initial_len = temp.path().metadata().unwrap().len();
    assert_eq!(initial_len, 128);

    // Create section with 1000 byte capacity
    storage.create_section("test", 1000, 0).unwrap();

    // File should now be 128 + 1000 = 1128 bytes
    let after_len = temp.path().metadata().unwrap().len();
    assert_eq!(after_len, 1128);

    // Verify EOF >= next_data_offset invariant holds
    assert!(storage.header().next_data_offset <= after_len);
}

#[test]
fn test_flush_table_after_all_data() {
    let temp = NamedTempFile::new().unwrap();

    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    // Create sections totaling 500 bytes
    storage.create_section("s1", 200, 0).unwrap();
    storage.create_section("s2", 300, 0).unwrap();

    // File should be 128 + 500 = 628 bytes
    let before_flush_len = temp.path().metadata().unwrap().len();
    assert_eq!(before_flush_len, 628);

    // Flush writes table (2 entries * 64 bytes = 128 bytes)
    storage.flush().unwrap();

    // File should be 628 + 128 = 756 bytes
    let after_flush_len = temp.path().metadata().unwrap().len();
    assert_eq!(after_flush_len, 756);

    // Table starts at 628 (after all data)
    assert_eq!(storage.header().section_table_offset, 628);
}

#[test]
fn test_validate_detects_missing_physical_reservation() {
    let temp = NamedTempFile::new().unwrap();

    // Create valid file with a section
    {
        let mut storage = SectionedStorage::create(temp.path()).unwrap();
        storage.create_section("data", 1000, 0).unwrap();
        storage.write_section("data", b"test data").unwrap();
        storage.flush().unwrap();
    }

    // Truncate the file to simulate corruption
    {
        let file = std::fs::OpenOptions::new()
            .write(true)
            .open(temp.path())
            .unwrap();
        file.set_len(200).unwrap(); // Less than next_data_offset (1128)
    }

    let result = SectionedStorage::open(temp.path());
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    // When file is truncated, we get a read error before validation
    // This is acceptable behavior - we detect the corruption either way
    assert!(
        err.contains("Failed to read section entry")
            || err.contains("truncated")
            || err.contains("next_data_offset"),
        "Error should mention read failure or truncation, got: {}",
        err
    );
}

#[test]
fn test_create_section_overflow_detected() {
    let temp = NamedTempFile::new().unwrap();

    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    // Try to create a section that would overflow next_data_offset
    let huge_capacity = u64::MAX - 100;
    let result = storage.create_section("huge", huge_capacity, 0);

    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(
        err.contains("overflow") || err.contains("reserve"),
        "Error should mention overflow or reserve failure, got: {}",
        err
    );
}

// ============================================================================
// CRITICAL BUG REGRESSION TEST: ALLOCATION AFTER FLUSH
// ============================================================================

#[test]
fn test_create_section_after_flush_does_not_overlap_live_table() {
    let temp = NamedTempFile::new().unwrap();

    // Step 1: Create file with first section
    let mut storage = SectionedStorage::create(temp.path()).unwrap();
    storage.create_section("s1", 200, 0).unwrap();
    storage.write_section("s1", b"data1").unwrap();

    let s1_offset = storage.get_section("s1").unwrap().offset;
    assert_eq!(s1_offset, 128); // First section after header

    // Step 2: Flush to write table
    storage.flush().unwrap();

    let table_offset = storage.header().section_table_offset;
    let file_len_after_flush = temp.path().metadata().unwrap().len();

    // Table should be right after s1's capacity
    assert_eq!(table_offset, 128 + 200);
    assert_eq!(file_len_after_flush, table_offset + 64); // 1 entry * 64 bytes

    // Step 3: Create another section AFTER flush
    // This is where the bug would occur: allocation must be after EOF
    storage.create_section("s2", 150, 0).unwrap();

    let s2 = storage.get_section("s2").unwrap();

    // CRITICAL: s2 must start at or after the old file length
    // (which includes the live table)
    assert!(
        s2.offset >= file_len_after_flush,
        "s2 offset {} must be >= old file len {} to avoid table overlap",
        s2.offset,
        file_len_after_flush
    );

    // Verify file was not truncated (should have grown)
    let file_len_after_create = temp.path().metadata().unwrap().len();
    assert!(
        file_len_after_create >= file_len_after_flush,
        "File should not shrink during create_section"
    );

    // File should be extended by s2's capacity
    assert_eq!(file_len_after_create, file_len_after_flush + 150);

    // Step 4: Flush again
    storage.flush().unwrap();

    // Step 5: Reopen and verify both sections are readable
    {
        let mut storage2 = SectionedStorage::open(temp.path()).unwrap();

        assert_eq!(storage2.section_count(), 2);

        let s1_data = storage2.read_section("s1").unwrap();
        assert_eq!(s1_data, b"data1");

        let s2_data = storage2.read_section("s2").unwrap();
        // s2 has no data written yet, should be empty
        assert_eq!(s2_data.len(), 0);

        // Verify new table offset
        let new_table_offset = storage2.header().section_table_offset;
        assert!(
            new_table_offset > table_offset,
            "New table should be after old table"
        );
    }

    // Write data to s2 and verify still works
    storage.write_section("s2", b"data2").unwrap();
    storage.flush().unwrap();

    {
        let mut storage3 = SectionedStorage::open(temp.path()).unwrap();
        let s2_data = storage3.read_section("s2").unwrap();
        assert_eq!(s2_data, b"data2");
    }
}

#[test]
fn test_multiple_flush_cycles_preserve_data() {
    let temp = NamedTempFile::new().unwrap();

    // Cycle 1: Create s1, flush
    {
        let mut storage = SectionedStorage::create(temp.path()).unwrap();
        storage.create_section("s1", 100, 0).unwrap();
        storage.write_section("s1", b"cycle1").unwrap();
        storage.flush().unwrap();
    }

    let len_after_cycle1 = temp.path().metadata().unwrap().len();

    // Cycle 2: Open, create s2, flush
    {
        let mut storage = SectionedStorage::open(temp.path()).unwrap();
        storage.create_section("s2", 100, 0).unwrap();
        storage.write_section("s2", b"cycle2").unwrap();
        storage.flush().unwrap();
    }

    let len_after_cycle2 = temp.path().metadata().unwrap().len();
    assert!(len_after_cycle2 > len_after_cycle1);

    // Cycle 3: Open, create s3, flush
    {
        let mut storage = SectionedStorage::open(temp.path()).unwrap();
        storage.create_section("s3", 100, 0).unwrap();
        storage.write_section("s3", b"cycle3").unwrap();
        storage.flush().unwrap();
    }

    let len_after_cycle3 = temp.path().metadata().unwrap().len();
    assert!(len_after_cycle3 > len_after_cycle2);

    // Final: Verify all data survived
    {
        let mut storage = SectionedStorage::open(temp.path()).unwrap();
        assert_eq!(storage.section_count(), 3);
        assert_eq!(storage.read_section("s1").unwrap(), b"cycle1");
        assert_eq!(storage.read_section("s2").unwrap(), b"cycle2");
        assert_eq!(storage.read_section("s3").unwrap(), b"cycle3");
    }
}

#[test]
fn test_allocation_base_uses_max_of_next_offset_and_file_len() {
    let temp = NamedTempFile::new().unwrap();

    // Create and flush to get table at EOF
    let mut storage = SectionedStorage::create(temp.path()).unwrap();
    storage.create_section("s1", 100, 0).unwrap();
    storage.flush().unwrap();

    let file_len = temp.path().metadata().unwrap().len();
    let next_offset = storage.header().next_data_offset;

    // After flush, file_len > next_offset (due to table)
    assert!(file_len > next_offset);

    // Create section - should use file_len as base
    storage.create_section("s2", 50, 0).unwrap();

    let s2 = storage.get_section("s2").unwrap();

    // s2 must start at file_len (not at next_offset)
    assert_eq!(
        s2.offset, file_len,
        "Section should be allocated at EOF, not at next_data_offset"
    );
}

// ============================================================================
// SECTION NAME VALIDATION TESTS
// ============================================================================

#[test]
fn test_empty_section_name_rejected() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    let result = storage.create_section("", 100, 0);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("empty"));
}

#[test]
fn test_section_name_32_bytes_accepted() {
    let name_32_bytes = "12345678901234567890123456789012";
    assert_eq!(name_32_bytes.len(), 32);

    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    storage.create_section(name_32_bytes, 100, 0).unwrap();

    // Verify section was actually created and reserved
    assert_eq!(storage.section_count(), 1);
    assert!(storage.get_section(name_32_bytes).is_some());
}

#[test]
fn test_section_name_33_bytes_rejected() {
    let name_33_bytes = "123456789012345678901234567890123";
    assert_eq!(name_33_bytes.len(), 33);

    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    let result = storage.create_section(name_33_bytes, 100, 0);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("too long"));
}

#[test]
fn test_section_name_multibyte_utf8() {
    // UTF-8 multibyte characters count by bytes, not chars
    let name_with_emoji = "a🔥"; // 'a' (1) + emoji (4) = 5 bytes
    assert_eq!(name_with_emoji.len(), 5);
    assert_eq!(name_with_emoji.chars().count(), 2);

    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    // Should be accepted (5 bytes < 32)
    storage.create_section(name_with_emoji, 100, 0).unwrap();
}

// ============================================================================
// DIRTY STATE VALIDATION TEST
// ============================================================================

#[test]
fn test_validate_dirty_state_rejected() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    // Create section without flushing
    storage.create_section("data", 100, 0).unwrap();

    // Now dirty = true, validation should fail
    let result = storage.validate();
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("dirty"));
}

#[test]
fn test_validate_after_flush_succeeds() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    storage.create_section("data", 100, 0).unwrap();
    storage.write_section("data", b"test").unwrap();
    storage.flush().unwrap();

    // After flush, validation should succeed
    storage.validate().unwrap();
}

// ============================================================================
// EMPTY SECTION TEST
// ============================================================================

#[test]
fn test_empty_section_can_be_read() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    storage.create_section("empty", 100, 0).unwrap();
    // Don't write any data

    let data = storage.read_section("empty").unwrap();
    assert_eq!(data.len(), 0);
}

// ============================================================================
// GET_SECTION AND LIST_SECTIONS TESTS
// ============================================================================

#[test]
fn test_get_section() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    storage.create_section("test", 100, 0).unwrap();

    let section = storage.get_section("test");
    assert!(section.is_some());
    assert_eq!(section.unwrap().capacity, 100);

    assert!(storage.get_section("nonexistent").is_none());
}

#[test]
fn test_list_sections() {
    let temp = NamedTempFile::new().unwrap();
    let mut storage = SectionedStorage::create(temp.path()).unwrap();

    storage.create_section("s1", 100, 0).unwrap();
    storage.create_section("s2", 200, 0).unwrap();
    storage.create_section("s3", 300, 0).unwrap();

    let sections = storage.list_sections();
    assert_eq!(sections.len(), 3);

    // BTreeMap sorts by key
    assert_eq!(sections[0].name, "s1");
    assert_eq!(sections[1].name, "s2");
    assert_eq!(sections[2].name, "s3");
}

// ============================================================================
// PATH TEST
// ============================================================================

#[test]
fn test_path_method() {
    let temp = NamedTempFile::new().unwrap();
    let storage = SectionedStorage::create(temp.path()).unwrap();

    assert_eq!(storage.path(), temp.path());
}

// ============================================================================
// HEADER TEST
// ============================================================================

#[test]
fn test_header_access() {
    let temp = NamedTempFile::new().unwrap();
    let storage = SectionedStorage::create(temp.path()).unwrap();

    let header = storage.header();
    assert_eq!(header.version, 1);
    assert_eq!(&header.magic[..], &FILE_MAGIC[..]);
}
