use anyhow::Result;
use std::path::PathBuf;

mod formatter;
mod writer;

pub use formatter::*;
pub use writer::*;

/// A structure to hold URL data with optional status information
#[derive(Debug, Clone)]
pub struct UrlData {
    /// The URL string
    pub url: String,
    /// Optional status information (e.g., HTTP status code)
    pub status: Option<String>,
    /// Providers that reported this URL (sorted, deduped). Empty when unknown.
    pub sources: Vec<String>,
}

impl UrlData {
    /// Create a new URL data entry without status information
    pub fn new(url: String) -> Self {
        UrlData {
            url,
            status: None,
            sources: Vec::new(),
        }
    }

    /// Create a new URL data entry with status information
    pub fn with_status(url: String, status: String) -> Self {
        UrlData {
            url,
            status: Some(status),
            sources: Vec::new(),
        }
    }

    /// Attach the list of providers that reported this URL. The input is
    /// sorted and deduplicated so output ordering is deterministic.
    pub fn with_sources(mut self, mut sources: Vec<String>) -> Self {
        sources.sort();
        sources.dedup();
        self.sources = sources;
        self
    }

    /// Parse a URL data entry from a string
    ///
    /// Can handle strings in the format "{url} - {status}" or plain URLs
    pub fn from_string(data: String) -> Self {
        // Parse strings in the format "{url} - {status}" if possible
        if let Some((url, status)) = data.split_once(" - ") {
            UrlData {
                url: url.to_string(),
                status: Some(status.to_string()),
                sources: Vec::new(),
            }
        } else {
            // No status information found
            UrlData {
                url: data,
                status: None,
                sources: Vec::new(),
            }
        }
    }
}

/// Interface for URL output handlers that can format and write URL data
pub trait Outputter: Send + Sync {
    /// Format a URL data entry to a string
    fn format(&self, url_data: &UrlData, is_last: bool) -> String;

    /// Output URL data to console or file
    fn output(&self, urls: &[UrlData], output_path: Option<PathBuf>, silent: bool) -> Result<()>;
}

/// Create an appropriate outputter based on the specified format
///
/// Supported formats:
/// - "json": JSON format with URL and optional status
/// - "csv": CSV format with URL and optional status
/// - any other value: Plain text format with one URL per line
pub fn create_outputter(format: &str) -> Box<dyn Outputter> {
    match format.to_lowercase().as_str() {
        "json" => Box::new(JsonOutputter::new()),
        "csv" => Box::new(CsvOutputter::new()),
        _ => Box::new(PlainOutputter::new()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_outputter_json() {
        let outputter = create_outputter("json");
        // Checks the output of the format method
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(
            outputter.format(&url_data, false),
            "{\"url\":\"https://example.com\"},"
        );
    }

    #[test]
    fn test_create_outputter_csv() {
        let outputter = create_outputter("csv");
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(outputter.format(&url_data, false), "https://example.com\n");
    }

    #[test]
    fn test_create_outputter_plain() {
        let outputter = create_outputter("plain");
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(outputter.format(&url_data, false), "https://example.com\n");
    }

    #[test]
    fn test_create_outputter_default_for_unknown() {
        let outputter = create_outputter("unknown");
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(outputter.format(&url_data, false), "https://example.com\n");
    }

    #[test]
    fn test_create_outputter_case_insensitive() {
        let json_outputter = create_outputter("JSON");
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(
            json_outputter.format(&url_data, false),
            "{\"url\":\"https://example.com\"},"
        );

        let csv_outputter = create_outputter("CSV");
        assert_eq!(
            csv_outputter.format(&url_data, false),
            "https://example.com\n"
        );
    }

    #[test]
    fn test_url_data_from_string() {
        let url_only = UrlData::from_string("https://example.com".to_string());
        assert_eq!(url_only.url, "https://example.com");
        assert_eq!(url_only.status, None);

        let with_status = UrlData::from_string("https://example.com - 200 OK".to_string());
        assert_eq!(with_status.url, "https://example.com");
        assert_eq!(with_status.status, Some("200 OK".to_string()));
    }

    #[test]
    fn test_url_data_new() {
        let url_data = UrlData::new("https://example.com/path".to_string());
        assert_eq!(url_data.url, "https://example.com/path");
        assert_eq!(url_data.status, None);
    }

    #[test]
    fn test_url_data_with_status() {
        let url_data = UrlData::with_status(
            "https://example.com".to_string(),
            "404 Not Found".to_string(),
        );
        assert_eq!(url_data.url, "https://example.com");
        assert_eq!(url_data.status, Some("404 Not Found".to_string()));
    }

    #[test]
    fn test_url_data_from_string_multiple_dashes() {
        // Test URL that contains " - " in the path should be correctly parsed
        let with_status = UrlData::from_string(
            "https://example.com/path-to-page - 301 Moved Permanently".to_string(),
        );
        assert_eq!(with_status.url, "https://example.com/path-to-page");
        assert_eq!(
            with_status.status,
            Some("301 Moved Permanently".to_string())
        );
    }

    #[test]
    fn test_url_data_from_string_with_complex_status() {
        let with_status = UrlData::from_string(
            "https://example.com/api/v1/users?id=123 - 500 Internal Server Error".to_string(),
        );
        assert_eq!(with_status.url, "https://example.com/api/v1/users?id=123");
        assert_eq!(
            with_status.status,
            Some("500 Internal Server Error".to_string())
        );
    }

    #[test]
    fn test_url_data_clone() {
        let original =
            UrlData::with_status("https://example.com".to_string(), "200 OK".to_string());
        let cloned = original.clone();

        assert_eq!(original.url, cloned.url);
        assert_eq!(original.status, cloned.status);
    }

    #[test]
    fn test_url_data_debug() {
        let url_data = UrlData::new("https://example.com".to_string());
        let debug_str = format!("{:?}", url_data);
        assert!(debug_str.contains("https://example.com"));
    }

    #[test]
    fn test_url_data_with_sources_sorts_and_dedupes() {
        let data = UrlData::new("https://example.com".to_string()).with_sources(vec![
            "wayback".into(),
            "otx".into(),
            "wayback".into(),
            "cc".into(),
        ]);
        assert_eq!(data.sources, vec!["cc", "otx", "wayback"]);
    }

    #[test]
    fn test_create_outputter_empty_format() {
        let outputter = create_outputter("");
        let url_data = UrlData::new("https://example.com".to_string());
        // Empty format should default to plain
        assert_eq!(outputter.format(&url_data, false), "https://example.com\n");
    }

    #[test]
    fn test_create_outputter_mixed_case() {
        let outputter = create_outputter("JsOn");
        let url_data = UrlData::new("https://example.com".to_string());
        assert_eq!(
            outputter.format(&url_data, false),
            "{\"url\":\"https://example.com\"},"
        );
    }
}
