use std::{io::Read, path::PathBuf};

use sgf::{parse_sgf, GameRecord};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    /*
    #[error("Database permission denied")]
    PermissionDenied,
    */
    #[error("An IO error occurred: {0}")]
    IOError(std::io::Error),
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Error::IOError(err)
    }
}

#[derive(Debug)]
pub struct Database {
    games: Vec<GameRecord>,
}

impl Database {
    pub fn open_path(path: PathBuf) -> Result<Database, Error> {
        let mut games: Vec<GameRecord> = Vec::new();

        let extension = PathBuf::from("sgf").into_os_string();

        let path_iter = std::fs::read_dir(path.clone())?;
        for entry in path_iter {
            match entry {
                Ok(entry) => {
                    if entry.path().extension() == Some(&extension) {
                        let mut buffer = String::new();
                        std::fs::File::open(entry.path())
                            .unwrap()
                            .read_to_string(&mut buffer)
                            .unwrap();
                        match parse_sgf(&buffer) {
                            Ok(sgfs) => {
                                let mut sgfs =
                                    sgfs.into_iter().flatten().collect::<Vec<sgf::GameRecord>>();
                                games.append(&mut sgfs);
                            }
                            Err(err) => println!("Error parsing {:?}: {:?}", entry.path(), err),
                        }
                    }
                }
                Err(err) => println!("failed entry: {:?}", err),
            }
        }

        Ok(Database { games })
    }

    pub fn all_games(&self) -> impl Iterator<Item = &GameRecord> {
        self.games.iter()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use cool_asserts::assert_matches;
    use sgf::Date;

    #[test]
    fn it_reads_empty_database() {
        let db = Database::open_path(PathBuf::from("fixtures/empty_database/"))
            .expect("database to open");
        assert_eq!(db.all_games().count(), 0);
    }

    #[ignore]
    #[test]
    fn it_reads_five_games_from_database() {
        let db =
            Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open");
        assert_eq!(db.all_games().count(), 5);

        assert_matches!(db.all_games().find(|g| g.black_player.name == Some("Steve".to_owned())),
        Some(game) => {
            assert_eq!(game.black_player.name, Some("Steve".to_owned()));
            assert_eq!(game.white_player.name, Some("Savanni".to_owned()));
            assert_eq!(game.dates, vec![Date::Date(chrono::NaiveDate::from_ymd_opt(2023, 4, 19).unwrap())]);
            // assert_eq!(game.info.komi, Some(6.5));
        }
        );
    }
}
