use std::io::Stdout;

use serde::Serialize;
use strum::Display;

use crate::cli::output::{CsvWriter, JsonLinesWriter, JsonWriter, Writer};

#[derive(clap::ValueEnum, Clone, Copy, Display, Default, Eq, PartialEq)]
pub enum OutputFormat {
    #[strum(serialize = "csv")]
    #[default]
    Csv,

    #[strum(serialize = "json")]
    Json,

    #[strum(serialize = "json-lines")]
    JsonLines,
}

impl Writer for OutputFormat {
    fn write_typenames<I>(&self, names: I) -> anyhow::Result<()>
    where
        I: Iterator<Item = String>,
    {
        match self {
            OutputFormat::Csv => CsvWriter.write_typenames(names),
            OutputFormat::Json => JsonWriter.write_typenames(names),
            OutputFormat::JsonLines => JsonLinesWriter.write_typenames(names),
        }
    }
}

impl OutputFormat {
    pub(crate) fn create_writer<T>(&self) -> anyhow::Result<OutputWriter<T>>
    where
        T: Serialize,
    {
        match self {
            OutputFormat::Csv => {
                let csv_writer = csv::WriterBuilder::new()
                    .flexible(false)
                    .from_writer(std::io::stdout());
                Ok(OutputWriter::Csv(Box::new(csv_writer)))
            }
            OutputFormat::Json => Ok(OutputWriter::Json(Vec::new())),
            OutputFormat::JsonLines => Ok(OutputWriter::JsonLines),
        }
    }
}

pub(crate) enum OutputWriter<T>
where
    T: Serialize,
{
    Csv(Box<csv::Writer<Stdout>>),
    Json(Vec<T>),
    JsonLines,
}

impl<T> OutputWriter<T>
where
    T: Serialize,
{
    pub fn write(&mut self, obj: T) -> anyhow::Result<()> {
        match self {
            OutputWriter::Csv(writer) => {
                writer.serialize(obj)?;
                writer.flush()?;
            }
            OutputWriter::Json(items) => {
                items.push(obj);
            }
            OutputWriter::JsonLines => {
                println!("{}", serde_json::to_string(&obj)?);
            }
        }
        Ok(())
    }
}

impl<T> Drop for OutputWriter<T>
where
    T: Serialize,
{
    fn drop(&mut self) {
        match self {
            OutputWriter::Csv(_) => (),
            OutputWriter::Json(items) => {
                println!("{}", serde_json::to_string_pretty(&items).unwrap());
            }
            OutputWriter::JsonLines => (),
        }
    }
}
