use crate::message::Card;
use crate::storage::{StorageProvider, StorageError};
use std::fs;

#[derive(Debug)]
pub struct FileCard {
    path: Option<String>
}

impl FileCard {
    pub fn new() -> Self {
        Self {
            path: None
        }
    }

    fn write_file(path: &str, text: &str) -> Result<(), StorageError> {
        let data = text.as_bytes();

        match fs::write(path, data) {
            Ok(_) => Ok(()),
            Err(e) => {
                let msg = format!("Cannot write to file \"{}\"; {}", path, e);
                Err(StorageError::ReadError(msg))
            }
        }
    }
}

impl StorageProvider for FileCard {
    fn using(&mut self, card: Card) -> Result<(), StorageError> {
        match card {
            Card::FileCard { path: card_path } => {
                self.path = Some(card_path);
                Ok(())
            },
            _ => {
                Err(StorageError::InvalidCard(card))
            }
        }
    }

    fn read(&self) -> Result<String, StorageError> {
        let Some(ref path) = self.path else {
            return Err(StorageError::ReadError("File path is unset".to_string()));
        };

        match fs::read_to_string(path) {
            Ok(text) => Ok(text),
            Err(e) => {
                let msg = format!("Cannot read file \"{}\": {}", path, e);
                Err(StorageError::ReadError(msg))
            }
        }
    }

    fn write(&mut self, new_text: &str) -> Result<(), StorageError> {
        match &self.path {
            Some(path) => Self::write_file(path, new_text),
            None => {
                panic!("Cannot read: no card in reader. You should invoke using() before write()");
            }
        }
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    pub fn when_reading_a_missing_file_then_return_an_error() {
        // Arrange
        const EXPECTED_ERROR: &str = "Cannot read file \"missing_file.txt\": No such file or directory (os error 2)";
        let card = super::FileCard { path: Some("missing_file.txt".to_string()) };

        // Act
        let result = card.read();
        
        // Assert
        match result {
            Err(e) => assert_eq!(StorageError::ReadError(EXPECTED_ERROR.to_string()), e),
            _ => assert!(false, "Reading the card should have failed")
        }
    }

    #[test]
    pub fn when_reading_a_file_then_return_its_content() {
        const CONTENT: &str = "This is is card content.";
        const PATH: &str = "file_card__when_reading_a_file_then_return_its_content.txt";

        // Arrange

        let _ = std::fs::remove_file(PATH);
        let mut card = super::FileCard { path: Some(PATH.to_string()) };
        let _ = card.write(CONTENT);

        // Act

        let result = card.read();

        // Assert

        match result {
            Ok(content) => assert_eq!(CONTENT.to_string(), content, "Unexpected file content"),
            Err(e) => panic!("Error reading card: {}", e)
        }

        // Clean

        let _ = std::fs::remove_file(PATH);
    }

    #[test]
    pub fn when_writing_a_file_then_return_an_ok_on_success() {
        const CONTENT: &str = "This is is card content.";
        const PATH: &str = "file_card__when_writing_a_file_then_return_an_ok_on_success.txt";

        // Arrange

        let _ = std::fs::remove_file(PATH);
        let mut card = super::FileCard { path: Some(PATH.to_string()) };

        // Act

        let result = card.write(CONTENT);

        // Assert

        assert_eq!(Ok(()), result, "Error writing to card");

        match card.read() {
            Ok(content) => assert_eq!(CONTENT.to_string(), content, "Unexpected file content"),
            Err(e) => panic!("Error reading card: {}", e)
        }

        // Clean

        let _ = std::fs::remove_file(PATH);
    }
}
