use std::io::{self, BufRead, BufReader, Write};
use std::fs::{OpenOptions, File};
use anyhow::{Context, Result};
use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("IO錯誤發生：{0}")]
    Io(#[from] io::Error),

    #[error("輸入指令錯誤")]
    InvalidCommand,
}

enum Command {
    Exit,
    Show,
    Calc(i32),
    Invalid(String),
}

impl Command {
    fn parse(input: &str) -> Self {
        let s = input.trim().to_lowercase();
        match s.as_str() {
            "exit" => Command::Exit,
            "show" => Command::Show,
            _ => {
                if let Ok(n) = s.parse::<i32>() {
                    if n > 0 {
                        return Command::Calc(n);
                    }
                }
                Command::Invalid(input.trim().to_string())
            }
        }
    }
}

struct FileManager {
    path: String,
}

impl FileManager {
    fn new(path: &str) -> Self {
        Self { path: path.to_string() }
    }

    fn append(&self, contents: &str) -> Result<()> {
        let mut file = OpenOptions::new()
            .append(true)
            .create(true)
            .open(&self.path)
            .with_context(|| format!("打開檔案 {} 失敗", &self.path))?;

        writeln!(file, "{}", contents).context("寫入檔案失敗")?;
        Ok(())
    }

    fn read_all(&self) -> Result<String> {
        let file = File::open(&self.path).with_context(|| format!("開啟檔案 {} 失敗", &self.path))?;
        let mut buf_reader = BufReader::new(file);
        let mut contents = String::new();
        buf_reader.read_to_string(&mut contents)?;
        Ok(contents)
    }
}

fn process_calc(num: i32) -> (String, i32) {
    let squares: Vec<_> = (1..=num).map(|x| (x, x * x)).collect();
    let sum: i32 = squares.iter().map(|&(_, sq)| sq).sum();

    let mut record = format!("計算 {} 的平方:\n", num);
    for (val, sq) in &squares {
        record += &format!("{} 的平方是 {}\n", val, sq);
    }
    record += &format!("平方和為 {}\n\n", sum);

    (record, sum)
}

fn handle_command(cmd: Command, file_manager: &FileManager) -> Result<bool> {
    match cmd {
        Command::Exit => {
            file_manager.append("使用者輸入 exit，程式結束。")?;
            println!("程式結束，再見!");
            Ok(false)
        }
        Command::Show => {
            let content = file_manager.read_all()?;
            if content.trim().is_empty() {
                println!("檔案為空");
            } else {
                println!("===== 檔案內容開始 =====");
                println!("{}", content);
                println!("===== 檔案內容結束 =====");
            }
            Ok(true)
        }
        Command::Calc(num) => {
            let (record, sum) = process_calc(num);
            println!("{}", record);
            file_manager.append(&record)?;
            println!("總和為 {}", sum);
            Ok(true)
        }
        Command::Invalid(cmd) => {
            println!("未識別指令：{}", cmd);
            file_manager.append(&format!("輸入錯誤指令：{}", cmd))?;
            Ok(true)
        }
    }
}

fn main() -> Result<()> {
    let file_manager = FileManager::new("output.txt");

    println!("歡迎使用平方計算程式");
    println!("可輸入正整數來計算平方與平方和並紀錄");
    println!("輸入 'show' 顯示檔案內容，輸入 'exit' 離開程式\n");

    let stdin = io::stdin();
    let mut reader = stdin.lock();

    loop {
        print!("請輸入指令或數字：");
        io::stdout().flush()?;

        let mut input = String::new();
        reader.read_line(&mut input)?;

        let cmd = Command::parse(&input);

        if !handle_command(cmd, &file_manager)? {
            break;
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::sync::Mutex;
    use once_cell::sync::Lazy;

    // 全域 Mutex 確保多執行緒測試同步寫檔
    static TEST_LOG_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

    fn log_to_file(content: &str) {
        let _lock = TEST_LOG_LOCK.lock().unwrap();
        let fm = FileManager::new("test_errors.log");
        fm.append(content).unwrap();
    }

    #[test]
    fn test_invalid_command() {
        let cmd = Command::parse("not_a_command");
        match cmd {
            Command::Invalid(s) => assert_eq!(s, "not_a_command"),
            _ => {
                let err_msg = "test_invalid_command 失敗：預期是 Invalid 指令。";
                log_to_file(err_msg);
                panic!("{}", err_msg);
            }
        }
    }

    #[test]
    fn test_valid_commands() {
        assert!(matches!(Command::parse("exit"), Command::Exit));
        assert!(matches!(Command::parse("show"), Command::Show));
        assert!(matches!(Command::parse("100"), Command::Calc(100)));
    }

    #[test]
    fn test_read_all_error() {
        let fm = FileManager::new("this_file_should_not_exist.txt");
        let res = fm.read_all();
        if res.is_err() {
            log_to_file("test_read_all_error 錯誤：檔案不存在");
        }
        assert!(res.is_err(), "應該錯誤，檔案不存在");
    }

    #[test]
    fn test_append_error() {
        let fm = FileManager::new("/root/no_permission.txt");
        let res = fm.append("試圖寫入失敗");
        if res.is_err() {
            log_to_file("test_append_error 錯誤：預期因無權限寫入失敗");
        }
        assert!(res.is_err(), "應該寫入失敗因為沒有權限");
    }

    #[test]
    fn test_handle_invalid_command() {
        let fm = FileManager::new("test_output.txt");
        let _ = fs::remove_file("test_output.txt");

        let res = handle_command(Command::Invalid("badcmd".to_string()), &fm).unwrap();
        assert!(res, "Invalid 指令應該返回繼續(true)");

        let content = fm.read_all().unwrap();
        assert!(content.contains("badcmd"));
    }

    #[test]
    fn test_handle_exit_command() {
        let fm = FileManager::new("test_output.txt");
        let res = handle_command(Command::Exit, &fm).unwrap();
        assert!(!res, "Exit 指令應回傳 false，表示結束");
    }
}


cargo test可以加入動畫嗎,每一條顯示測試中然後一條條慢速ok,