use std::fs;

use termichart_core::{Candle, DataAdapter, Point, Result, TermichartError};

/// A [`DataAdapter`] that parses candle or point data from CSV text.
pub struct CsvAdapter {
    data: String,
}

impl CsvAdapter {
    /// Creates a new `CsvAdapter` from a raw CSV string (including headers).
    pub fn from_str(s: &str) -> Self {
        Self {
            data: s.to_owned(),
        }
    }

    /// Creates a new `CsvAdapter` by reading the contents of a file.
    pub fn from_file(path: &str) -> Result<Self> {
        let data = fs::read_to_string(path)?;
        Ok(Self { data })
    }
}

impl DataAdapter for CsvAdapter {
    fn candles(&self) -> Result<Vec<Candle>> {
        let mut reader = csv::Reader::from_reader(self.data.as_bytes());
        let mut candles = Vec::new();

        for result in reader.records() {
            let record = result.map_err(|e| TermichartError::ParseError(e.to_string()))?;

            let time: f64 = record
                .get(0)
                .ok_or_else(|| TermichartError::ParseError("missing time field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let open: f64 = record
                .get(1)
                .ok_or_else(|| TermichartError::ParseError("missing open field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let high: f64 = record
                .get(2)
                .ok_or_else(|| TermichartError::ParseError("missing high field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let low: f64 = record
                .get(3)
                .ok_or_else(|| TermichartError::ParseError("missing low field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let close: f64 = record
                .get(4)
                .ok_or_else(|| TermichartError::ParseError("missing close field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let volume: f64 = record
                .get(5)
                .ok_or_else(|| TermichartError::ParseError("missing volume field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            candles.push(Candle {
                time,
                open,
                high,
                low,
                close,
                volume,
            });
        }

        Ok(candles)
    }

    fn points(&self) -> Result<Vec<Point>> {
        let mut reader = csv::Reader::from_reader(self.data.as_bytes());
        let mut points = Vec::new();

        for result in reader.records() {
            let record = result.map_err(|e| TermichartError::ParseError(e.to_string()))?;

            let x: f64 = record
                .get(0)
                .ok_or_else(|| TermichartError::ParseError("missing x field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            let y: f64 = record
                .get(1)
                .ok_or_else(|| TermichartError::ParseError("missing y field".into()))?
                .trim()
                .parse()
                .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;

            points.push(Point { x, y });
        }

        Ok(points)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_candles_from_csv() {
        let csv = "time,open,high,low,close,volume\n1.0,10.0,15.0,9.0,14.0,100.0\n2.0,14.0,16.0,13.0,15.0,200.0\n";
        let adapter = CsvAdapter::from_str(csv);
        let candles = adapter.candles().unwrap();
        assert_eq!(candles.len(), 2);
        assert_eq!(candles[0].open, 10.0);
        assert_eq!(candles[1].close, 15.0);
    }

    #[test]
    fn parse_points_from_csv() {
        let csv = "x,y\n1.0,2.0\n3.0,4.0\n";
        let adapter = CsvAdapter::from_str(csv);
        let points = adapter.points().unwrap();
        assert_eq!(points.len(), 2);
        assert_eq!(points[0].x, 1.0);
        assert_eq!(points[1].y, 4.0);
    }

    #[test]
    fn missing_field_returns_parse_error() {
        let csv = "time,open,high,low,close\n1.0,10.0,15.0,9.0,14.0\n";
        let adapter = CsvAdapter::from_str(csv);
        assert!(adapter.candles().is_err());
    }
}
