use super::{
    csv_row::CsvRow,
    dynamic_state::{vato007::ingey::types::HostCsvReader, DynamicState, ReadMapData},
};
use csv::{Reader, StringRecord};
use polars::datatypes::AnyValue;
use polars::prelude::{col, lit, LazyCsvReader, LazyFileListReader};
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::io::Read;

pub struct CsvReader {
    path: String,
    reader: Reader<File>,
}

impl CsvReader {
    pub fn new(path: String) -> Self {
        let reader = Reader::from_path(&path).expect("Failed to create csv reader");
        CsvReader { path, reader }
    }
}

impl HostCsvReader for DynamicState {
    fn columns(&mut self, self_: wasmtime::component::Resource<CsvReader>) -> Vec<String> {
        let resource = self
            .resources
            .get_mut(&self_)
            .expect("Failed to find resource");
        if resource.reader.has_headers() {
            resource
                .reader
                .headers()
                .expect("Reader says it has headers but doesn't")
                .iter()
                .map(|element| element.to_owned())
                .collect()
        } else {
            vec![]
        }
    }

    // TODO: These next methods needs to be cleaned up badly
    fn next(
        &mut self,
        self_: wasmtime::component::Resource<CsvReader>,
    ) -> Result<wasmtime::component::Resource<CsvRow>, String> {
        let resource = self
            .resources
            .get_mut(&self_)
            .expect("Failed to find resource");
        let mut buf = StringRecord::new();
        let result = resource.reader.read_record(&mut buf);
        match result {
            Ok(read) => {
                if read {
                    let mut record_map = BTreeMap::new();
                    if resource.reader.has_headers() {
                        resource
                            .reader
                            .headers()
                            .expect("Reader says it has headers but doesn't")
                            .iter()
                            .enumerate()
                            .for_each(|(i, name)| {
                                record_map.insert(name.to_owned(), buf.get(i).unwrap().to_owned());
                            });
                    }
                    let result = self
                        .resources
                        .push(CsvRow { values: record_map })
                        .expect("");
                    Ok(result)
                } else {
                    Err("No more records available to read".to_owned())
                }
            }
            Err(err) => Err(err.to_string()),
        }
    }

    fn next_into_map(
        &mut self,
        self_: wasmtime::component::Resource<CsvReader>,
    ) -> wasmtime::component::Resource<ReadMapData> {
        let resource = self
            .resources
            .get_mut(&self_)
            .expect("Failed to find resource");
        let mut buf = StringRecord::new();
        let result = resource.reader.read_record(&mut buf);
        let record_map = match result {
            Ok(read) => {
                if read {
                    let mut record_map = HashMap::new();
                    if resource.reader.has_headers() {
                        resource
                            .reader
                            .headers()
                            .expect("Reader says it has headers but doesn't")
                            .iter()
                            .enumerate()
                            .for_each(|(i, name)| {
                                record_map.insert(name.to_owned(), buf.get(i).unwrap().to_owned());
                            });
                    }
                    record_map
                } else {
                    HashMap::new()
                }
            }
            Err(_) => HashMap::new(),
        };
        self.resources
            .push(ReadMapData { data: record_map })
            .expect("")
    }

    fn has_next(&mut self, self_: wasmtime::component::Resource<CsvReader>) -> bool {
        let resource = self
            .resources
            .get_mut(&self_)
            .expect("Failed to find resource");
        !resource.reader.is_done()
    }

    // TODO: Clean this up as well
    #[doc = " Get a row by values in one or more columns"]
    fn query(
        &mut self,
        self_: wasmtime::component::Resource<CsvReader>,
        values: Vec<(String, String)>,
    ) -> wasmtime::component::Resource<CsvRow> {
        let resource = self
            .resources
            .get_mut(&self_)
            .expect("Failed to find resource");
        let mut df = LazyCsvReader::new(&resource.path)
            .finish()
            .expect("Failed to open file");
        for (key, value) in values {
            df = df.filter(col(key).eq(lit(value)));
        }
        let result = df.collect().expect("Failed to filter file");
        let mut record_map = BTreeMap::new();

        if let Ok(row) = result.get_row(0) {
            for field in result.fields() {
                let column_index = result.get_column_index(field.name()).unwrap();
                let value: &AnyValue = row.0.get(column_index).unwrap();
                record_map.insert(field.name.to_string(), value.to_string());
            }
        }
        self.resources
            .push(CsvRow { values: record_map })
            .expect("Failed to create csv row")
    }

    fn read_into_string(&mut self, self_: wasmtime::component::Resource<CsvReader>) -> String {
        let resource = self.resources.get(&self_).expect("Failed to find resource");
        let mut file = File::open(&resource.path).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap_or(0);
        contents
    }

    fn drop(&mut self, rep: wasmtime::component::Resource<CsvReader>) -> wasmtime::Result<()> {
        self.resources.delete(rep)?;
        Ok(())
    }
}
