// SPDX-FileCopyrightText: 2025 The superseedr Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

use clap::{Parser, Subcommand};
use sha1::{Digest, Sha1};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
    pub input: Option<String>,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    Add { input: String },
    StopClient,
}

pub fn process_input(input_str: &str, watch_path: &Path) {
    if input_str.starts_with("magnet:") {
        let hash_bytes = Sha1::digest(input_str.as_bytes());
        let file_hash_hex = hex::encode(hash_bytes);

        let final_filename = format!("{}.magnet", file_hash_hex);
        let final_path = watch_path.join(final_filename);
        let temp_filename = format!("{}.magnet.tmp", file_hash_hex);
        let temp_path = watch_path.join(temp_filename);

        tracing::info!(
            "Attempting to write magnet link to temporary path: {:?}",
            temp_path
        );
        match fs::write(&temp_path, input_str.as_bytes()) {
            Ok(_) => {
                tracing::info!(
                    "Atomically renaming magnet file to final path: {:?}",
                    final_path
                );
                if let Err(e) = fs::rename(&temp_path, &final_path) {
                    tracing::error!("Failed to atomically rename magnet file: {}", e);
                }
            }
            Err(e) => {
                tracing::error!("Failed to write magnet file to temporary path: {}", e);
            }
        }
    } else {
        let torrent_path = PathBuf::from(input_str);
        match fs::canonicalize(&torrent_path) {
            Ok(absolute_path) => {
                let hash_bytes = Sha1::digest(absolute_path.to_string_lossy().as_bytes());
                let file_hash_hex = hex::encode(hash_bytes);
                let final_filename = format!("{}.path", file_hash_hex);
                let final_dest_path = watch_path.join(final_filename);
                let temp_filename = format!("{}.path.tmp", file_hash_hex);
                let temp_dest_path = watch_path.join(temp_filename);

                let absolute_path_cow = absolute_path.to_string_lossy();
                let content = absolute_path_cow.as_bytes();

                tracing::info!(
                    "Attempting to write torrent path to temporary path: {:?}",
                    temp_dest_path
                );
                match fs::write(&temp_dest_path, content) {
                    Ok(_) => {
                        tracing::info!(
                            "Atomically renaming path file to final path: {:?}",
                            final_dest_path
                        );
                        if let Err(e) = fs::rename(&temp_dest_path, &final_dest_path) {
                            tracing::error!("Failed to atomically rename path file: {}", e);
                        }
                    }
                    Err(e) => {
                        tracing::error!("Failed to write path file to temporary path: {}", e);
                    }
                }
            }
            Err(e) => {
                // Don't treat as error if launched by macOS without a valid path
                if !input_str.starts_with("magnet:") {
                    // Avoid logging error for magnet links here
                    tracing::warn!(
                        "Input '{}' is not a valid torrent file path: {}",
                        input_str,
                        e
                    );
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::{self, File};
    use std::io::Write;

    // Helper to setup a temp directory if tempfile crate is missing
    fn setup_temp_dir() -> (PathBuf, impl Drop) {
        let dir = std::env::temp_dir().join(format!("superseedr_test_{}", rand::random::<u32>()));
        fs::create_dir_all(&dir).unwrap();
        let dir_clone = dir.clone();
        // Return a dropper to clean up
        struct Cleaner(PathBuf);
        impl Drop for Cleaner {
            fn drop(&mut self) {
                let _ = fs::remove_dir_all(&self.0);
            }
        }
        (dir, Cleaner(dir_clone))
    }

    #[test]
    fn test_process_input_magnet() {
        let (watch_dir, _cleaner) = setup_temp_dir();
        let magnet_link = "magnet:?xt=urn:btih:5b63529350414441534441534441534441534441";

        process_input(magnet_link, &watch_dir);

        // Calculate expected hash
        let hash_bytes = Sha1::digest(magnet_link.as_bytes());
        let expected_name = format!("{}.magnet", hex::encode(hash_bytes));
        let expected_path = watch_dir.join(expected_name);

        assert!(expected_path.exists(), "Magnet file should exist");
        let content = fs::read_to_string(expected_path).unwrap();
        assert_eq!(
            content, magnet_link,
            "File content should be the magnet link"
        );
    }

    #[test]
    fn test_process_input_torrent_path() {
        let (watch_dir, _cleaner) = setup_temp_dir();

        // 1. Create a dummy torrent file to "add"
        let torrent_source_name = "test_linux.torrent";
        let torrent_source_path = watch_dir.join(torrent_source_name);
        {
            let mut f = File::create(&torrent_source_path).unwrap();
            f.write_all(b"dummy torrent content").unwrap();
        }
        let abs_source_path = fs::canonicalize(&torrent_source_path).unwrap();

        // 2. Process the path input
        process_input(abs_source_path.to_str().unwrap(), &watch_dir);

        // 3. Verify the .path file was created
        // The filename is the hash of the *path string*
        let hash_bytes = Sha1::digest(abs_source_path.to_string_lossy().as_bytes());
        let expected_name = format!("{}.path", hex::encode(hash_bytes));
        let expected_path_file = watch_dir.join(expected_name);

        assert!(expected_path_file.exists(), ".path file should be created");

        // 4. Verify content matches the source path
        let content = fs::read_to_string(expected_path_file).unwrap();
        assert_eq!(
            content,
            abs_source_path.to_string_lossy(),
            ".path file should contain the absolute path"
        );
    }

    #[test]
    fn test_process_invalid_path() {
        let (watch_dir, _cleaner) = setup_temp_dir();
        // Pass a non-existent path
        let bad_path = "/path/to/nonexistent/file.torrent";

        // Should not panic
        process_input(bad_path, &watch_dir);

        // Verify directory is empty (no .path file created)
        let count = fs::read_dir(&watch_dir).unwrap().count();
        assert_eq!(count, 0, "No files should be created for invalid input");
    }
}
