use std::iter::zip;

use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

#[derive(Debug, Deserialize)]
pub struct CsvWrapper {
	pub items: serde_json::Value,
}

impl Serialize for CsvWrapper {
	fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		self.items.serialize(serializer)
	}
}

pub fn load_csv(csv_bytes: &[u8]) -> Result<CsvWrapper> {
	let mut reader = csv::Reader::from_reader(csv_bytes);

	let header: Vec<String> = match reader.headers() {
		Ok(headers) => headers.iter().map(|s| s.to_string()).collect(),
		Err(e) => return Result::Err(e.into()),
	};
	let mut items = Vec::new();
	for record_result in reader.records() {
		let mut record_map = serde_json::Map::new();
		for (key, value) in zip(header.clone(), &record_result?) {
			record_map.insert(key, parse_value(value.trim()));
		}
		items.push(serde_json::Value::Object(record_map));
	}

	Ok(CsvWrapper { items: serde_json::Value::Array(items) })
}

fn parse_value(s: &str) -> JsonValue {
	if s.is_empty() {
		return JsonValue::Null;
	}
	if let Ok(b) = s.parse::<bool>() {
		return JsonValue::Bool(b);
	}
	if let Ok(i) = s.parse::<i64>() {
		return JsonValue::Number(i.into());
	}
	if let Ok(f) = s.parse::<f64>()
		&& f.is_finite()
	{
		return JsonValue::Number(serde_json::Number::from_f64(f).unwrap());
	}
	JsonValue::String(s.to_string())
}

pub fn json_to_csv(json: &[u8]) -> Result<Vec<u8>> {
	let json: JsonValue = serde_json::from_slice(json)?;

	if let JsonValue::Array(values) = json {
		let mut buffer = String::new();
		let mut dump_header: bool = true;

		for item in values {
			if let JsonValue::Object(items) = item {
				let keys: Vec<String> = items.keys().cloned().collect();
				if dump_header {
					dump_header = false;
					let mut first = true;
					for key in keys.clone() {
						if first {
							first = false;
						} else {
							buffer.push(',');
						}
						buffer.push_str(&key.to_string());
					}
					buffer.push('\n');
				}
				let mut first = true;
				for key in keys {
					if let Some(value) = items.get(&key) {
						if first {
							first = false;
						} else {
							buffer.push(',');
						}
						buffer.push_str(&value.to_string());
					} else {
						bail!("Missing value for key: in {key:?}")
					}
				}
			} else {
				bail!("Invalid json format for csv conversion: {item:?}")
			}
			buffer.push('\n');
		}
		Ok(buffer.into_bytes())
	} else {
		bail!("Invalid json format for csv conversion. Expected root Array of items.")
	}
}
