use std::io::Write;

use crate::FwWriterSchema;
use crate::fw_error::Result;
use crate::fw_writer_options::FwWriterOptions;
use crate::fw_writer_state::FwWriterState;

/// A fixed-width format writer.
///
/// Writes records to a destination implementing [`Write`]. Construct one via
/// [`FwWriter::options`]:
///
/// ```
/// use fwf::{FwWriter, FwWriterField, FwWriterSchema, RecordSeparator};
///
/// let schema = FwWriterSchema::builder()
///     .field(FwWriterField::builder(5).build())
///     .separator(RecordSeparator::Lf)
///     .build();
///
/// let mut buf = Vec::new();
/// let mut writer = FwWriter::options(schema).from_writer(&mut buf);
///
/// writer.write_byte_record(&[b"Alice"]).unwrap();
/// drop(writer);
/// assert_eq!(buf, b"Alice\n");
/// ```
#[derive(Debug)]
pub struct FwWriter<W> {
    writer: W,
    state: FwWriterState,
}

impl FwWriter<()> {
    /// Creates a new [`FwWriterOptions`] for the given schema.
    #[must_use]
    #[inline]
    pub fn options(schema: FwWriterSchema) -> FwWriterOptions {
        FwWriterOptions::new(schema)
    }
}

impl<W> FwWriter<W> {
    /// Creates a new writer. Called by [`FwWriterOptions`].
    pub(crate) fn new(writer: W, state: FwWriterState) -> Self {
        Self { writer, state }
    }

    /// Swaps the schema driving this writer and returns the new writer paired
    /// with the previous schema.
    ///
    /// The underlying destination is preserved, and the previous schema's
    /// internal buffer is returned along with it so it can be reused without
    /// reallocation. Use this to alternate between schemas when emitting
    /// multi-record-shape formats.
    #[must_use]
    pub fn into_writer_for_schema(self, schema: FwWriterSchema) -> (FwWriter<W>, FwWriterSchema) {
        let (new_state, old_schema) = self.state.for_schema(schema);
        let new_writer = Self {
            writer: self.writer,
            state: new_state,
        };
        (new_writer, old_schema)
    }
}

impl<W: Write> FwWriter<W> {
    /// Returns the number of records successfully written so far.
    #[must_use]
    #[inline]
    pub fn record_number(&self) -> u64 {
        self.state.record_number()
    }

    /// Flushes the underlying writer.
    ///
    /// # Errors
    ///
    /// Returns [`crate::FwfError::Io`] if the flush fails.
    #[inline]
    pub fn flush(&mut self) -> Result<()> {
        self.writer.flush()?;
        Ok(())
    }

    /// Consumes the writer and returns the underlying writer.
    #[must_use]
    #[inline]
    pub fn into_inner(self) -> W {
        self.writer
    }

    /// Writes a single record.
    ///
    /// The `values` slice must contain one entry per field in the schema.
    /// To produce a padding-only column, pass an empty `b""` value.
    ///
    /// # Errors
    ///
    /// Returns [`crate::FwfError::ValueCountMismatch`] if `values.len()` does not
    /// match the number of fields.
    ///
    /// Returns [`crate::FwfError::ValueTooLong`] if any value exceeds its field width,
    /// and the effective truncation policy is [`crate::TruncationPolicy::Error`].
    ///
    /// Returns [`crate::FwfError::Io`] on write failure.
    pub fn write_byte_record(&mut self, values: &[&[u8]]) -> Result<()> {
        self.state.prepare_byte_record(values)?;
        self.writer.write_all(self.state.record_buffer())?;
        Ok(())
    }

    /// Writes a single record from string values.
    ///
    /// Each string is encoded using the configured encoding before writing.
    /// The `values` slice must contain one entry per field in the schema.
    /// To produce a padding-only column, pass an empty `""` value.
    ///
    /// # Errors
    ///
    /// Returns [`crate::FwfError::ValueCountMismatch`] if `values.len()` does not
    /// match the number of fields.
    ///
    /// Returns [`crate::FwfError::ValueTooLong`] if any encoded value exceeds its
    /// field width and the effective truncation policy is
    /// [`crate::TruncationPolicy::Error`].
    ///
    /// Returns [`crate::FwfError::Io`] on write failure.
    #[cfg(feature = "encoding_rs")]
    pub fn write_string_record(&mut self, values: &[&str]) -> Result<()> {
        self.state.prepare_string_record(values)?;
        self.writer.write_all(self.state.record_buffer())?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Alignment;
    use crate::FwfError;
    use crate::TruncationPolicy;
    use crate::fw_writer_field::FwWriterField;
    use crate::record_separator::RecordSeparator;

    fn name_age_schema() -> FwWriterSchema {
        FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .field(FwWriterField::builder(2).build())
            .separator(RecordSeparator::Lf)
            .build()
    }

    fn name_age_writer(dest: &mut Vec<u8>) -> FwWriter<&mut Vec<u8>> {
        FwWriter::options(name_age_schema()).from_writer(dest)
    }

    // -- Basic writing --

    #[test]
    fn write_single_record() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        writer.write_byte_record(&[b"Alice", b"30"]).unwrap();
        assert_eq!(writer.record_number(), 1);
        drop(writer);
        assert_eq!(buf, b"Alice30\n");
    }

    #[test]
    fn write_multiple_records() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        writer.write_byte_record(&[b"Alice", b"30"]).unwrap();
        writer.write_byte_record(&[b"Bob", b"25"]).unwrap();
        assert_eq!(writer.record_number(), 2);
        drop(writer);
        assert_eq!(buf, b"Alice30\nBob  25\n");
    }

    // -- Padding --

    #[test]
    fn write_left_aligned_padding() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        writer.write_byte_record(&[b"Bob", b"25"]).unwrap();
        assert_eq!(buf, b"Bob  25\n");
    }

    #[test]
    fn write_right_aligned_padding() {
        let schema = FwWriterSchema::builder()
            .field(
                FwWriterField::builder(5)
                    .alignment(Alignment::Right)
                    .build(),
            )
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Bob"]).unwrap();
        assert_eq!(buf, b"  Bob");
    }

    #[test]
    fn write_custom_padding_byte() {
        let schema = FwWriterSchema::builder()
            .field(
                FwWriterField::builder(5)
                    .padding(b'0')
                    .alignment(Alignment::Right)
                    .build(),
            )
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"42"]).unwrap();
        assert_eq!(buf, b"00042");
    }

    // -- Padding-only fields --

    #[test]
    fn write_with_padding_field() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .field(FwWriterField::builder(3).build())
            .field(FwWriterField::builder(2).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alice", b"", b"30"]).unwrap();
        assert_eq!(buf, b"Alice   30");
    }

    #[test]
    fn write_with_custom_padding_field() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(3).build())
            .field(FwWriterField::builder(2).padding(b'|').build())
            .field(FwWriterField::builder(3).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"ABC", b"", b"XYZ"]).unwrap();
        assert_eq!(buf, b"ABC||XYZ");
    }

    // -- Truncation --

    #[test]
    fn write_value_too_long_error() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        let err = writer
            .write_byte_record(&[b"Alexandria", b"30"])
            .unwrap_err();
        assert!(matches!(
            err,
            FwfError::ValueTooLong {
                field_index: 0,
                value_len: 10,
            }
        ));
    }

    #[test]
    fn write_truncate_right() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .truncation(TruncationPolicy::Right)
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alexandria"]).unwrap();
        assert_eq!(buf, b"Alexa");
    }

    #[test]
    fn write_truncate_left() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .truncation(TruncationPolicy::Left)
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alexandria"]).unwrap();
        assert_eq!(buf, b"ndria");
    }

    #[test]
    fn write_field_truncation_overrides_global() {
        let schema = FwWriterSchema::builder()
            .field(
                FwWriterField::builder(5)
                    .truncation(TruncationPolicy::Right)
                    .build(),
            )
            .truncation(TruncationPolicy::Error)
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alexandria"]).unwrap();
        assert_eq!(buf, b"Alexa");
    }

    // -- Value count mismatch --

    #[test]
    fn write_too_few_values() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        let err = writer.write_byte_record(&[b"Alice"]).unwrap_err();
        assert!(matches!(
            err,
            FwfError::ValueCountMismatch {
                provided: 1,
                expected: 2
            }
        ));
    }

    #[test]
    fn write_too_many_values() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        let err = writer
            .write_byte_record(&[b"Alice", b"30", b"extra"])
            .unwrap_err();
        assert!(matches!(
            err,
            FwfError::ValueCountMismatch {
                provided: 3,
                expected: 2
            }
        ));
    }

    // -- Separators --

    #[test]
    fn write_no_separator() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alice"]).unwrap();
        writer.write_byte_record(&[b"Bob"]).unwrap();
        assert_eq!(buf, b"AliceBob  ");
    }

    #[test]
    fn write_crlf_separator() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .separator(RecordSeparator::CrLf)
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alice"]).unwrap();
        assert_eq!(buf, b"Alice\r\n");
    }

    #[test]
    fn write_custom_separator() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .separator(RecordSeparator::Custom(b"||".to_vec()))
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b"Alice"]).unwrap();
        assert_eq!(buf, b"Alice||");
    }

    // -- Record number --

    #[test]
    fn record_number_starts_at_zero() {
        let writer = FwWriter::options(FwWriterSchema::builder().build()).from_writer(Vec::new());
        assert_eq!(writer.record_number(), 0);
    }

    // -- Exact fit (no padding needed) --

    #[test]
    fn write_exact_fit_value() {
        let mut buf = Vec::new();
        let mut writer = name_age_writer(&mut buf);

        writer.write_byte_record(&[b"Alice", b"30"]).unwrap();
        assert_eq!(buf, b"Alice30\n");
    }

    // -- Empty value --

    #[test]
    fn write_empty_value() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_byte_record(&[b""]).unwrap();
        assert_eq!(buf, b"     ");
    }
}

#[cfg(test)]
#[cfg(feature = "encoding_rs")]
mod encoding_tests {
    use super::*;
    use crate::FwfError;
    use crate::TruncationPolicy;
    use crate::fw_writer_field::FwWriterField;
    use crate::record_separator::RecordSeparator;

    fn truncating_field_schema(width: usize, truncation: TruncationPolicy) -> FwWriterSchema {
        FwWriterSchema::builder()
            .field(FwWriterField::builder(width).build())
            .truncation(truncation)
            .build()
    }

    #[test]
    fn write_string_record_ascii() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .field(FwWriterField::builder(2).build())
            .separator(RecordSeparator::Lf)
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_string_record(&["Alice", "30"]).unwrap();
        writer.write_string_record(&["Bob", "25"]).unwrap();
        assert_eq!(buf, b"Alice30\nBob  25\n");
    }

    #[test]
    fn write_string_record_utf8() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(6).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        writer.write_string_record(&["café"]).unwrap();
        assert_eq!(&buf, "café ".as_bytes());
    }

    #[test]
    fn write_string_record_too_long_encoded() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(3).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        let err = writer.write_string_record(&["café"]).unwrap_err();
        assert!(matches!(
            err,
            FwfError::ValueTooLong {
                field_index: 0,
                value_len: 5,
            }
        ));
    }

    #[test]
    fn write_string_record_value_count_mismatch() {
        let schema = FwWriterSchema::builder()
            .field(FwWriterField::builder(5).build())
            .field(FwWriterField::builder(2).build())
            .build();
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(schema).from_writer(&mut buf);

        let err = writer.write_string_record(&["Alice"]).unwrap_err();
        assert!(matches!(
            err,
            FwfError::ValueCountMismatch {
                provided: 1,
                expected: 2
            }
        ));
    }

    #[test]
    fn write_string_truncate_right() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(5, TruncationPolicy::Right))
            .from_writer(&mut buf);

        writer.write_string_record(&["Alexandria"]).unwrap();
        assert_eq!(buf, b"Alexa");
    }

    #[test]
    fn write_string_truncate_right_multibyte() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(4, TruncationPolicy::Right))
            .from_writer(&mut buf);

        writer.write_string_record(&["café"]).unwrap();
        assert_eq!(buf, b"caf ");
    }

    #[test]
    fn write_string_truncate_left_multibyte() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(4, TruncationPolicy::Left))
            .from_writer(&mut buf);

        writer.write_string_record(&["élan"]).unwrap();
        assert_eq!(buf, b"lan ");
    }

    #[test]
    fn write_string_truncate_right_no_partial_codepoint() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(4, TruncationPolicy::Right))
            .from_writer(&mut buf);

        writer.write_string_record(&["A😀B"]).unwrap();
        assert_eq!(buf, b"A   ");
    }

    #[test]
    fn write_string_truncate_left_no_partial_codepoint() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(4, TruncationPolicy::Left))
            .from_writer(&mut buf);

        writer.write_string_record(&["A😀B"]).unwrap();
        assert_eq!(buf, b"B   ");
    }

    #[test]
    fn write_string_truncate_right_multibyte_exact_boundary() {
        let mut buf = Vec::new();
        let mut writer = FwWriter::options(truncating_field_schema(5, TruncationPolicy::Right))
            .from_writer(&mut buf);

        writer.write_string_record(&["😀AB"]).unwrap();
        assert_eq!(buf, "😀A".as_bytes());
    }
}
