use std::io::Write;

use crate::chopper::error::Result;
use crate::chopper::types::{Header, Row};
use crate::sink::csv_output_config::{CSVOutputConfig, TimestampStyle};
use crate::sink::sink::Sink;
use crate::sink::util::{check_header, check_row};
use crate::util::csv::util::write_quoted;
use crate::util::timestamp_units::TimestampUnits;

pub struct CSVSink<W: 'static + Write> {
    writer: W,
    csv_output_config: CSVOutputConfig,
    field_count: usize,
}

impl<W: 'static + Write> CSVSink<W> {
    pub fn new(header: &Header, writer: W, csv_output_config: CSVOutputConfig) -> Result<Self> {
        let mut sink = CSVSink {
            writer,
            csv_output_config,
            field_count: 0,
        };

        sink.write_csv_header(header)?;

        Ok(sink)
    }

    fn write_csv_header(&mut self, header: &Header) -> Result<()> {
        self.field_count = check_header(header)?;

        let writer = &mut self.writer;
        let field_name = header.field_names().clone();
        let mut first = true;

        if self.csv_output_config.print_time_col() {
            write!(writer, "{},", self.csv_output_config.time_col_name())?;
        }
        for name in field_name {
            if first {
                write!(writer, "{}", name)?;
                first = false;
            } else {
                write!(writer, ",{}", name)?;
            }
        }
        write!(writer, "\n")?;
        Ok(())
    }

    pub fn inner(self) -> W {
        self.writer
    }
}

impl<W: 'static + Write + Send> Sink for CSVSink<W> {
    fn write(&mut self, row: Box<Row>) -> Result<()> {
        check_row(&row, self.field_count)?;

        let mut first_col = true;
        if self.csv_output_config.print_time_col() {
            let time = match self.csv_output_config.time_col_style() {
                TimestampStyle::Epoch => {
                    let time = match self.csv_output_config.time_col_units() {
                        TimestampUnits::Seconds => row.timestamp / 1_000_000_000,
                        TimestampUnits::Millis => row.timestamp / 1_000_000,
                        TimestampUnits::Micros => row.timestamp / 1_000,
                        TimestampUnits::Nanos => row.timestamp,
                    };
                    time.to_string()
                }
                TimestampStyle::HumanReadable => {
                    let format = match self.csv_output_config.time_col_units() {
                        TimestampUnits::Seconds => "%Y-%m-%dT%H:%M:%S%:z",
                        TimestampUnits::Millis => "%Y-%m-%dT%H:%M:%S%.3f%:z",
                        TimestampUnits::Micros => "%Y-%m-%dT%H:%M:%S%.6f%:z",
                        TimestampUnits::Nanos => "%Y-%m-%dT%H:%M:%S%.9f%:z",
                    };
                    let time = self.csv_output_config.timezone().timestamp(row.timestamp)?;
                    time.format(format).to_string()
                }
            };

            write!(self.writer, "{}", time)?;
            first_col = false;
        }
        let field_values = &row.field_values;
        let delimiter = self.csv_output_config.delimiter();
        for value in field_values {
            if first_col {
                first_col = false;
            } else {
                write!(self.writer, "{}", delimiter)?;
            }

            write_quoted(&mut self.writer, &value, delimiter)?;
        }
        write!(self.writer, "\n")?;
        Ok(())
    }

    fn flush(&mut self) -> Result<()> {
        self.writer.flush()?;
        Ok(())
    }
}
