use criterion::{Criterion, criterion_group, criterion_main};
use fwf::{
    Alignment, FwReader, FwReaderField, FwReaderSchema, FwWriter, FwWriterField, FwWriterSchema,
    RecordSeparator, TruncationPolicy,
};
use std::fs::File;
use std::io::{BufReader, Cursor};
use std::path::Path;

// ---------------------------------------------------------------------------
// Synthetic fixed-width file generator
// ---------------------------------------------------------------------------

const BENCH_FILE: &str = "benches/data/bench_large.fwf";
const RECORD_COUNT: usize = 100_000;
const RECORD_WIDTH: usize = 130;

/// Financial-transaction-style schema: 10 fields, 130 bytes per record.
///
/// | Field   | Width | Alignment | Padding |
/// |---------|-------|-----------|---------|
/// | ACCOUNT |    12 | Left      | ' '     |
/// | NAME    |    30 | Left      | ' '     |
/// | AMOUNT  |    12 | Right     | '0'     |
/// | DATE    |    10 | Left      | ' '     |
/// | TYPE    |     4 | Left      | ' '     |
/// | SEQ     |     8 | Right     | '0'     |
/// | STATUS  |     6 | Left      | ' '     |
/// | REF     |    16 | Left      | ' '     |
/// | BALANCE |    12 | Right     | '0'     |
/// | MEMO    |    20 | Left      | ' '     |
fn build_writer_schema() -> FwWriterSchema {
    FwWriterSchema::builder()
        .field(FwWriterField::builder(12).build()) // ACCOUNT
        .field(FwWriterField::builder(30).build()) // NAME
        .field(
            FwWriterField::builder(12)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // AMOUNT
        .field(FwWriterField::builder(10).build()) // DATE
        .field(FwWriterField::builder(4).build()) // TYPE
        .field(
            FwWriterField::builder(8)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // SEQ
        .field(FwWriterField::builder(6).build()) // STATUS
        .field(FwWriterField::builder(16).build()) // REF
        .field(
            FwWriterField::builder(12)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // BALANCE
        .field(FwWriterField::builder(20).build()) // MEMO
        .separator(RecordSeparator::Lf)
        .truncation(TruncationPolicy::Right)
        .build()
}

/// Reader schema matching the writer layout.
fn build_reader_schema() -> FwReaderSchema {
    FwReaderSchema::builder(RECORD_WIDTH)
        .field(FwReaderField::builder(0..12).build()) // ACCOUNT
        .field(FwReaderField::builder(12..42).build()) // NAME
        .field(
            FwReaderField::builder(42..54)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // AMOUNT
        .field(FwReaderField::builder(54..64).build()) // DATE
        .field(FwReaderField::builder(64..68).build()) // TYPE
        .field(
            FwReaderField::builder(68..76)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // SEQ
        .field(FwReaderField::builder(76..82).build()) // STATUS
        .field(FwReaderField::builder(82..98).build()) // REF
        .field(
            FwReaderField::builder(98..110)
                .alignment(Alignment::Right)
                .padding(b'0')
                .build(),
        ) // BALANCE
        .field(FwReaderField::builder(110..130).build()) // MEMO
        .separator(RecordSeparator::Lf)
        .build()
        .unwrap()
}

fn build_template_record() -> Vec<&'static [u8]> {
    vec![
        b"ACC-00123456",    // ACCOUNT (12, exact)
        b"John Q. Public",  // NAME    (14, padded to 30)
        b"123456.78",       // AMOUNT  (9, zero-padded to 12)
        b"2024-01-15",      // DATE    (10, exact)
        b"SALE",            // TYPE    (4, exact)
        b"42",              // SEQ     (2, zero-padded to 8)
        b"POSTED",          // STATUS  (6, exact)
        b"TXN-20240115-01", // REF     (15, padded to 16)
        b"98765.43",        // BALANCE (8, zero-padded to 12)
        b"Monthly payment", // MEMO    (15, padded to 20)
    ]
}

/// Generate a synthetic fixed-width file at the given path.
fn generate_fwf(path: &Path) {
    let file = File::create(path).expect("Failed to create benchmark file");
    let template = build_template_record();

    let mut writer =
        FwWriter::options(build_writer_schema()).from_writer(std::io::BufWriter::new(file));
    for _ in 0..RECORD_COUNT {
        writer.write_byte_record(&template).unwrap();
    }
    writer.flush().unwrap();
}

/// Ensure the synthetic file exists, regenerating if needed.
fn ensure_bench_file() -> std::path::PathBuf {
    let path = Path::new(BENCH_FILE);
    if !path.exists() {
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).expect("Failed to create benches/data directory");
        }
        eprintln!("Generating synthetic benchmark file ({RECORD_COUNT} records)...");
        generate_fwf(path);
        eprintln!(
            "Generated: {} ({} bytes)",
            path.display(),
            std::fs::metadata(path).unwrap().len()
        );
    }
    path.to_path_buf()
}

// ---------------------------------------------------------------------------
// Benchmarks
// ---------------------------------------------------------------------------

/// Write throughput to an in-memory buffer (no filesystem I/O).
fn write_to_vec(c: &mut Criterion) {
    let template = build_template_record();

    c.bench_function("write_to_vec", |b| {
        b.iter(|| {
            let mut buf = Vec::with_capacity(RECORD_COUNT * (RECORD_WIDTH + 1));
            {
                let mut writer = FwWriter::options(build_writer_schema()).from_writer(&mut buf);
                for _ in 0..RECORD_COUNT {
                    writer.write_byte_record(&template).unwrap();
                }
            }
            buf.len()
        });
    });
}

/// Read throughput using the `read_byte_record()` loop from a file.
fn read_byte_records(c: &mut Criterion) {
    let path = ensure_bench_file();

    c.bench_function("read_byte_records", |b| {
        b.iter(|| {
            let file = File::open(&path).unwrap();
            let mut reader = FwReader::options(build_reader_schema()).from_file(file);
            let mut count = 0u64;
            while reader.read_byte_record().unwrap().is_some() {
                count += 1;
            }
            assert_eq!(count, RECORD_COUNT as u64);
        });
    });
}

/// Read throughput using the `byte_records()` iterator from a file.
fn read_byte_iterator(c: &mut Criterion) {
    let path = ensure_bench_file();

    c.bench_function("read_byte_iterator", |b| {
        b.iter(|| {
            let file = File::open(&path).unwrap();
            let reader = FwReader::options(build_reader_schema()).from_file(file);
            let mut count = 0u64;
            for record in reader.byte_records() {
                let _record = record.unwrap();
                count += 1;
            }
            assert_eq!(count, RECORD_COUNT as u64);
        });
    });
}

/// Read throughput from an in-memory buffer, isolating from filesystem I/O.
fn read_from_cursor(c: &mut Criterion) {
    let path = ensure_bench_file();
    let file_bytes = std::fs::read(&path).unwrap();

    c.bench_function("read_from_cursor", |b| {
        b.iter(|| {
            let cursor = Cursor::new(file_bytes.as_slice());
            let buf_reader = BufReader::new(cursor);
            let mut reader = FwReader::options(build_reader_schema()).from_reader(buf_reader);
            let mut count = 0u64;
            while reader.read_byte_record().unwrap().is_some() {
                count += 1;
            }
            assert_eq!(count, RECORD_COUNT as u64);
        });
    });
}

// ---------------------------------------------------------------------------
// String benchmarks (encoding_rs feature)
// ---------------------------------------------------------------------------

#[cfg(feature = "encoding_rs")]
fn build_string_template_record() -> Vec<&'static str> {
    vec![
        "ACC-00123456",
        "John Q. Public",
        "123456.78",
        "2024-01-15",
        "SALE",
        "42",
        "POSTED",
        "TXN-20240115-01",
        "98765.43",
        "Monthly payment",
    ]
}

/// Write throughput using `write_string_record` to an in-memory buffer.
#[cfg(feature = "encoding_rs")]
fn write_string_to_vec(c: &mut Criterion) {
    let template = build_string_template_record();

    c.bench_function("write_string_to_vec", |b| {
        b.iter(|| {
            let mut buf = Vec::with_capacity(RECORD_COUNT * (RECORD_WIDTH + 1));
            {
                let mut writer = FwWriter::options(build_writer_schema()).from_writer(&mut buf);
                for _ in 0..RECORD_COUNT {
                    writer.write_string_record(&template).unwrap();
                }
            }
            buf.len()
        });
    });
}

/// Read throughput using the `read_string_record()` loop from a file.
#[cfg(feature = "encoding_rs")]
fn read_string_records(c: &mut Criterion) {
    let path = ensure_bench_file();

    c.bench_function("read_string_records", |b| {
        b.iter(|| {
            let file = File::open(&path).unwrap();
            let mut reader = FwReader::options(build_reader_schema()).from_file(file);
            let mut count = 0u64;
            while reader.read_string_record().unwrap().is_some() {
                count += 1;
            }
            assert_eq!(count, RECORD_COUNT as u64);
        });
    });
}

/// Read throughput using the `string_records()` iterator from a file.
#[cfg(feature = "encoding_rs")]
fn read_string_iterator(c: &mut Criterion) {
    let path = ensure_bench_file();

    c.bench_function("read_string_iterator", |b| {
        b.iter(|| {
            let file = File::open(&path).unwrap();
            let reader = FwReader::options(build_reader_schema()).from_file(file);
            let mut count = 0u64;
            for record in reader.string_records() {
                let _record = record.unwrap();
                count += 1;
            }
            assert_eq!(count, RECORD_COUNT as u64);
        });
    });
}

// ---------------------------------------------------------------------------
// Async benchmarks (tokio feature)
// ---------------------------------------------------------------------------

#[cfg(feature = "tokio")]
fn tokio_rt() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
}

/// Async write throughput to an in-memory buffer.
#[cfg(feature = "tokio")]
fn async_write_to_vec(c: &mut Criterion) {
    let template = build_template_record();
    let rt = tokio_rt();

    c.bench_function("async_write_to_vec", |b| {
        b.iter(|| {
            rt.block_on(async {
                let mut buf = Vec::with_capacity(RECORD_COUNT * (RECORD_WIDTH + 1));
                {
                    let mut writer =
                        FwWriter::options(build_writer_schema()).from_tokio_writer(&mut buf);
                    for _ in 0..RECORD_COUNT {
                        writer.write_byte_record(&template).await.unwrap();
                    }
                }
                buf.len()
            })
        });
    });
}

/// Async read throughput using `read_byte_record()` from a file.
#[cfg(feature = "tokio")]
fn async_read_byte_records(c: &mut Criterion) {
    let path = ensure_bench_file();
    let rt = tokio_rt();

    c.bench_function("async_read_byte_records", |b| {
        b.iter(|| {
            rt.block_on(async {
                let file = tokio::fs::File::open(&path).await.unwrap();
                let mut reader = FwReader::options(build_reader_schema()).from_tokio_file(file);
                let mut count = 0u64;
                while reader.read_byte_record().await.unwrap().is_some() {
                    count += 1;
                }
                assert_eq!(count, RECORD_COUNT as u64);
            });
        });
    });
}

/// Async string read throughput using `read_string_record()` from a file.
#[cfg(all(feature = "tokio", feature = "encoding_rs"))]
fn async_read_string_records(c: &mut Criterion) {
    let path = ensure_bench_file();
    let rt = tokio_rt();

    c.bench_function("async_read_string_records", |b| {
        b.iter(|| {
            rt.block_on(async {
                let file = tokio::fs::File::open(&path).await.unwrap();
                let mut reader = FwReader::options(build_reader_schema()).from_tokio_file(file);
                let mut count = 0u64;
                while reader.read_string_record().await.unwrap().is_some() {
                    count += 1;
                }
                assert_eq!(count, RECORD_COUNT as u64);
            });
        });
    });
}

// ---------------------------------------------------------------------------
// Criterion groups
// ---------------------------------------------------------------------------

#[cfg(not(any(feature = "encoding_rs", feature = "tokio")))]
criterion_group! {
    name = benches;
    config = Criterion::default().measurement_time(std::time::Duration::from_secs(10));
    targets =
        write_to_vec,
        read_byte_records,
        read_byte_iterator,
        read_from_cursor
}

#[cfg(all(feature = "encoding_rs", not(feature = "tokio")))]
criterion_group! {
    name = benches;
    config = Criterion::default().measurement_time(std::time::Duration::from_secs(10));
    targets =
        write_to_vec,
        read_byte_records,
        read_byte_iterator,
        read_from_cursor,
        write_string_to_vec,
        read_string_records,
        read_string_iterator
}

#[cfg(all(feature = "tokio", not(feature = "encoding_rs")))]
criterion_group! {
    name = benches;
    config = Criterion::default().measurement_time(std::time::Duration::from_secs(10));
    targets =
        write_to_vec,
        read_byte_records,
        read_byte_iterator,
        read_from_cursor,
        async_write_to_vec,
        async_read_byte_records
}

#[cfg(all(feature = "encoding_rs", feature = "tokio"))]
criterion_group! {
    name = benches;
    config = Criterion::default().measurement_time(std::time::Duration::from_secs(10));
    targets =
        write_to_vec,
        read_byte_records,
        read_byte_iterator,
        read_from_cursor,
        write_string_to_vec,
        read_string_records,
        read_string_iterator,
        async_write_to_vec,
        async_read_byte_records,
        async_read_string_records
}

criterion_main!(benches);
