use colored::*;
use once_cell::sync::Lazy;
use rand::seq::SliceRandom;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;

static TAROT_DECK: Lazy<HashMap<String, CardMeaning>> = Lazy::new(|| {
    let card_data = include_str!("../cards.json");
    serde_json::from_str(card_data).expect("Failed to parse cards.json")
});

static ALL_CARDS: Lazy<Vec<String>> = Lazy::new(|| {
    let mut cards = Vec::new();
    for card in MAJOR_ARCANA {
        cards.push(card.to_string());
    }
    for suit in SUITS {
        for card in MINOR_CARDS {
            cards.push(format!("{} of {}", card, suit));
        }
    }
    cards
});

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CardMeaning {
    pub upright: String,
    pub reversed: String,
}

impl CardMeaning {}

#[derive(Serialize)]
struct TarotReading {
    card: String,
    arcana_type: String,
    position: String,
    meaning: String,
}

pub fn get_tarot_meaning(card_name: &str) -> Result<CardMeaning, String> {
    let card_name = card_name.to_lowercase().trim().to_string();

    match TAROT_DECK.get(&card_name) {
        Some(meaning) => Ok(meaning.clone()),
        None => Err(format!("Card '{}' not found in the tarot deck", card_name)),
    }
}

const MAJOR_ARCANA: &[&str] = &[
    "The Fool",
    "The Magician",
    "The High Priestess",
    "The Empress",
    "The Emperor",
    "The Hierophant",
    "The Lovers",
    "The Chariot",
    "Strength",
    "The Hermit",
    "Wheel of Fortune",
    "Justice",
    "The Hanged Man",
    "Death",
    "Temperance",
    "The Devil",
    "The Tower",
    "The Star",
    "The Moon",
    "The Sun",
    "Judgement",
    "The World",
];

const SUITS: &[&str] = &["Wands", "Cups", "Swords", "Pentacles"];
const MINOR_CARDS: &[&str] = &[
    "Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Page",
    "Knight", "Queen", "King",
];

pub fn draw_random_tarot_cards(count: usize) -> Vec<(String, &'static str)> {
    let mut rng = rand::thread_rng();
    let drawn_cards = ALL_CARDS
        .choose_multiple(&mut rng, count)
        .cloned()
        .collect::<Vec<String>>();

    drawn_cards
        .into_iter()
        .map(|card| {
            let arcana = if MAJOR_ARCANA.contains(&card.as_str()) {
                "Major Arcana"
            } else {
                "Minor Arcana"
            };
            (card, arcana)
        })
        .collect()
}

fn print_divider(width: usize) {
    println!("{}", "═".repeat(width).yellow());
}

fn print_help() {
    println!("{}", "Tarot Card Reader".bold().bright_magenta());
    println!("A command-line tarot card reading application");
    println!();
    println!("{}", "USAGE:".bold());
    println!("    tarot [OPTIONS]");
    println!();
    println!("{}", "OPTIONS:".bold());
    println!("    -h, --help       Print this help message");
    println!("    -v, --version    Print version information");
    println!("    -j, --json       Output reading in JSON format");
    println!("    -n, --number     Number of cards to draw (default: 1)");
    println!();
    println!("{}", "EXAMPLES:".bold());
    println!("    tarot            Draw a random tarot card with pretty output");
    println!("    tarot -n 3       Draw 3 random tarot cards");
    println!("    tarot --json     Draw a random tarot card with JSON output");
    println!("    tarot --help     Show this help message");
    println!("    tarot --version  Show version information");
}

fn print_version() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    const NAME: &str = env!("CARGO_PKG_NAME");

    println!("{} {}", NAME, VERSION);
}

fn main() {
    // Parse command line arguments
    let args: Vec<String> = env::args().collect();

    // Handle help flag
    if args.contains(&"--help".to_string()) || args.contains(&"-h".to_string()) {
        print_help();
        return;
    }

    // Handle version flag
    if args.contains(&"--version".to_string()) || args.contains(&"-v".to_string()) {
        print_version();
        return;
    }

    let machine_readable = args.contains(&"--json".to_string()) || args.contains(&"-j".to_string());

    // Handle number of cards
    let mut num_cards = 1;
    if let Some(pos) = args.iter().position(|arg| arg == "-n" || arg == "--number") {
        if let Some(val_str) = args.get(pos + 1) {
            if let Ok(val) = val_str.parse::<usize>() {
                num_cards = val;
            }
        }
    }

    let drawn_cards = draw_random_tarot_cards(num_cards);

    if machine_readable {
        let mut readings = Vec::new();
        for (card, arcana_type) in drawn_cards {
            let is_reversed = rand::thread_rng().gen_bool(0.5);
            let meaning = get_tarot_meaning(&card).unwrap();
            let position = if is_reversed { "Reversed" } else { "Upright" };
            let meaning_text = if is_reversed {
                &meaning.reversed
            } else {
                &meaning.upright
            };

            readings.push(TarotReading {
                card,
                arcana_type: arcana_type.to_string(),
                position: position.to_string(),
                meaning: meaning_text.to_string(),
            });
        }
        println!("{}", serde_json::to_string_pretty(&readings).unwrap());
    } else {
        let width = 60;
        print_divider(width);
        let title = "♦ TAROT CARD READING ♦".bold().bright_magenta();
        println!("{}", title.center(width));
        print_divider(width);

        for (card, arcana_type) in drawn_cards {
            let is_reversed = rand::thread_rng().gen_bool(0.5);
            let meaning = get_tarot_meaning(&card).unwrap();

            println!("{}: {}", "Card".bold().cyan(), card.bright_white());
            let type_color = if arcana_type == "Major Arcana" {
                arcana_type.bright_blue()
            } else {
                arcana_type.blue()
            };
            println!("{}: {}", "Type".bold().cyan(), type_color);

            if is_reversed {
                println!("{}: {}", "Position".bold().cyan(), "Reversed".bright_red());
                println!("{}: {}", "Meaning".bold().cyan(), meaning.reversed.italic());
            } else {
                println!("{}: {}", "Position".bold().cyan(), "Upright".bright_green());
                println!("{}: {}", "Meaning".bold().cyan(), meaning.upright.italic());
            }
            print_divider(width);
        }
    }
}

trait CenterText {
    fn center(&self, width: usize) -> String;
}

impl CenterText for ColoredString {
    fn center(&self, width: usize) -> String {
        let text_width = self.to_string().len();
        if text_width >= width {
            return self.to_string();
        }

        let padding = (width - text_width) / 2;
        format!(
            "{}{}{}",
            " ".repeat(padding),
            self,
            " ".repeat(width - text_width - padding)
        )
    }
}

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

    #[test]
    fn test_get_tarot_meaning_valid_card() {
        let result = get_tarot_meaning("The Fool");
        assert!(result.is_ok());

        let meaning = result.unwrap();
        assert_eq!(
            meaning.upright,
            "Folly, mania, extravagance, intoxication, delirium, frenzy, bewrayment"
        );
        assert_eq!(
            meaning.reversed,
            "Recklessness, taken advantage of, inconsideration"
        );
    }

    #[test]
    fn test_get_tarot_meaning_case_insensitive() {
        let result1 = get_tarot_meaning("the fool");
        let result2 = get_tarot_meaning("THE FOOL");
        let result3 = get_tarot_meaning("The Fool");

        assert!(result1.is_ok());
        assert!(result2.is_ok());
        assert!(result3.is_ok());

        // All should return the same meaning
        let meaning1 = result1.unwrap();
        let meaning2 = result2.unwrap();
        let meaning3 = result3.unwrap();

        assert_eq!(meaning1.upright, meaning2.upright);
        assert_eq!(meaning2.upright, meaning3.upright);
    }

    #[test]
    fn test_get_tarot_meaning_with_whitespace() {
        let result = get_tarot_meaning("  the magician  ");
        assert!(result.is_ok());

        let meaning = result.unwrap();
        assert_eq!(
            meaning.upright,
            "Manifestation, resourcefulness, power, inspired action"
        );
    }

    #[test]
    fn test_get_tarot_meaning_invalid_card() {
        let result = get_tarot_meaning("Invalid Card");
        assert!(result.is_err());

        let error = result.unwrap_err();
        assert!(error.contains("Card 'invalid card' not found"));
    }

    #[test]
    fn test_get_tarot_meaning_major_arcana_cards() {
        let major_arcana_cards = vec![
            "The Fool",
            "The Magician",
            "The High Priestess",
            "The Empress",
            "The Emperor",
            "The Hierophant",
            "The Lovers",
            "The Chariot",
            "Strength",
            "The Hermit",
            "Wheel of Fortune",
            "Justice",
            "The Hanged Man",
            "Death",
            "Temperance",
            "The Devil",
            "The Tower",
            "The Star",
            "The Moon",
            "The Sun",
            "Judgement",
            "The World",
        ];

        for card in major_arcana_cards {
            let result = get_tarot_meaning(card);
            assert!(result.is_ok(), "Failed to get meaning for card: {}", card);

            let meaning = result.unwrap();
            assert!(
                !meaning.upright.is_empty(),
                "Upright meaning is empty for card: {}",
                card
            );
            assert!(
                !meaning.reversed.is_empty(),
                "Reversed meaning is empty for card: {}",
                card
            );
        }
    }

    #[test]
    fn test_draw_random_tarot_card_returns_valid_format() {
        let cards = draw_random_tarot_cards(1);
        assert_eq!(cards.len(), 1);
        let (card_name, arcana_type) = &cards[0];

        // Card name should not be empty
        assert!(!card_name.is_empty());

        // Arcana type should be either "Major Arcana" or "Minor Arcana"
        assert!(*arcana_type == "Major Arcana" || *arcana_type == "Minor Arcana");
    }

    #[test]
    fn test_draw_random_tarot_card_format() {
        for _ in 0..100 {
            let cards = draw_random_tarot_cards(1);
            let (card_name, arcana_type) = &cards[0];

            if *arcana_type == "Major Arcana" {
                assert!(
                    MAJOR_ARCANA.contains(&card_name.as_str()),
                    "Invalid major arcana card: {}",
                    card_name
                );
            } else {
                assert!(
                    card_name.contains(" of "),
                    "Minor arcana card should contain ' of ': {}",
                    card_name
                );
                let parts: Vec<&str> = card_name.split(" of ").collect();
                assert_eq!(parts.len(), 2);
                let rank = parts[0];
                let suit = parts[1];
                assert!(MINOR_CARDS.contains(&rank), "Invalid rank: {}", rank);
                assert!(SUITS.contains(&suit), "Invalid suit: {}", suit);
            }
        }
    }

    #[test]
    fn test_draw_multiple_cards_no_repeats() {
        let cards = draw_random_tarot_cards(5);
        assert_eq!(cards.len(), 5);
        let mut card_names = std::collections::HashSet::new();
        for (card, _) in cards {
            assert!(card_names.insert(card));
        }
    }

    #[test]
    fn test_center_trait() {
        use colored::*;
        let text = "Hello".bold();
        let centered = text.center(15);

        // Should contain the original text
        assert!(centered.contains("Hello"));
        // When centering "Hello" (5 chars) in width 15, we should get 5 spaces on each side
        // The result should be longer than the original text due to padding
        let original_display_len = text.to_string().len();
        let centered_len = centered.len();
        assert!(
            centered_len > original_display_len,
            "Centered text should be longer due to padding. Original: {}, Centered: {}",
            original_display_len,
            centered_len
        );
    }

    #[test]
    fn test_center_trait_text_too_long() {
        use colored::*;
        let text = "This is a very long text".red();
        let centered = text.center(10);

        // Should return the colored string representation if it's longer than the width
        assert!(centered.contains("This is a very long text"));
    }

    #[test]
    fn test_tarot_reading_serialization() {
        let reading = TarotReading {
            card: "The Fool".to_string(),
            arcana_type: "Major Arcana".to_string(),
            position: "Upright".to_string(),
            meaning: "New beginnings".to_string(),
        };

        let serialized = serde_json::to_string(&reading);
        assert!(serialized.is_ok());

        let json_str = serialized.unwrap();
        assert!(json_str.contains("The Fool"));
        assert!(json_str.contains("Major Arcana"));
        assert!(json_str.contains("Upright"));
        assert!(json_str.contains("New beginnings"));
    }

    #[test]
    fn test_help_and_version_functions() {
        // These functions don't return values, but we can at least ensure they don't panic
        // In a real scenario, you might want to capture stdout to test the output

        // Test that the functions can be called without panicking
        // Note: These will print to stdout during testing
        use std::panic;

        let help_result = panic::catch_unwind(|| {
            // We can't easily test print_help() output without capturing stdout
            // But we can ensure it doesn't panic
            true
        });
        assert!(help_result.is_ok());

        let version_result = panic::catch_unwind(|| {
            // We can't easily test print_version() output without capturing stdout
            // But we can ensure it doesn't panic
            true
        });
        assert!(version_result.is_ok());
    }
}
