//! Event type constants and metadata structures for the League Client API.
//!
//! The record-daemon stores raw API JSON data only — no typed event parsing.
//! Event types are simple string constants derived from the URI, used for
//! classification and state machine transitions. The tauri-app parses the
//! raw data it needs on the frontend side.

use serde::{Deserialize, Serialize};

// ============================================================================
// Event Type Constants
// ============================================================================

/// Match found in queue.
pub const EVENT_TYPE_MATCH_FOUND: &str = "match_found";
/// Champion select phase started.
pub const EVENT_TYPE_CHAMP_SELECT_START: &str = "champ_select_start";
/// Player picked a champion.
pub const EVENT_TYPE_CHAMPION_PICK: &str = "champion_pick";
/// Game has started.
pub const EVENT_TYPE_GAME_START: &str = "game_start";
/// Player killed an enemy.
pub const EVENT_TYPE_KILL: &str = "kill";
/// Player died.
pub const EVENT_TYPE_DEATH: &str = "death";
/// Objective was taken.
pub const EVENT_TYPE_OBJECTIVE: &str = "objective";
/// In-game stats update.
pub const EVENT_TYPE_STATS_UPDATE: &str = "stats_update";
/// Game has ended.
pub const EVENT_TYPE_GAME_END: &str = "game_end";
/// Gameflow phase changed.
pub const EVENT_TYPE_PHASE_CHANGE: &str = "phase_change";
/// LP changed after a ranked game.
pub const EVENT_TYPE_LP_CHANGE: &str = "lp_change";
/// Unknown / unclassified event.
pub const EVENT_TYPE_UNKNOWN: &str = "unknown";

/// Generate a brief human-readable description from raw event data.
///
/// This is used for logging and timeline display. It extracts minimal
/// information from the raw JSON — no full parsing required.
pub fn describe_event(event_type: &str, raw_data: &serde_json::Value) -> String {
    match event_type {
        EVENT_TYPE_MATCH_FOUND => {
            let queue = raw_data
                .get("queueType")
                .or_else(|| {
                    raw_data
                        .get("gameData")
                        .and_then(|gd| gd.get("queue"))
                        .and_then(|q| q.get("name"))
                })
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown");
            format!("Match found: {}", queue)
        }
        EVENT_TYPE_CHAMP_SELECT_START => "Champion select started".to_string(),
        EVENT_TYPE_CHAMPION_PICK => {
            let champ = raw_data
                .get("championName")
                .or_else(|| raw_data.get("championName"))
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown");
            format!("Champion picked: {}", champ)
        }
        EVENT_TYPE_GAME_START => {
            let game_id = raw_data
                .get("gameId")
                .or_else(|| raw_data.get("gameData").and_then(|gd| gd.get("gameId")))
                .and_then(|v| v.as_u64())
                .unwrap_or(0);
            format!("Game started: ID {}", game_id)
        }
        EVENT_TYPE_KILL => {
            let killer = raw_data
                .get("KillerName")
                .or_else(|| raw_data.get("killer"))
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown");
            let victim = raw_data
                .get("VictimName")
                .or_else(|| raw_data.get("victim"))
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown");
            format!("{} killed {}", killer, victim)
        }
        EVENT_TYPE_DEATH => {
            let cause = raw_data
                .get("DeathCause")
                .or_else(|| raw_data.get("cause"))
                .and_then(|v| v.as_str())
                .unwrap_or("Unknown");
            format!("Player died ({})", cause)
        }
        EVENT_TYPE_OBJECTIVE => {
            let obj_type = raw_data
                .get("EventName")
                .or_else(|| raw_data.get("objectiveType"))
                .and_then(|v| v.as_str())
                .unwrap_or("objective");
            let team = raw_data
                .get("Team")
                .or_else(|| raw_data.get("team"))
                .and_then(|v| v.as_u64())
                .map(|t| if t == 100 { "Blue" } else { "Red" })
                .unwrap_or("Unknown");
            format!("{} took {}", team, obj_type)
        }
        EVENT_TYPE_STATS_UPDATE => "Stats update".to_string(),
        EVENT_TYPE_GAME_END => {
            let victory = raw_data
                .get("victory")
                .and_then(|v| v.as_bool())
                .unwrap_or(false);
            let result = if victory { "Victory" } else { "Defeat" };
            format!("Game ended: {}", result)
        }
        EVENT_TYPE_PHASE_CHANGE => {
            let phase = raw_data
                .as_str()
                .or_else(|| raw_data.get("phase").and_then(|v| v.as_str()))
                .unwrap_or("Unknown");
            format!("Phase changed to: {}", phase)
        }
        EVENT_TYPE_LP_CHANGE => {
            // Prefer leaguePointsDelta from raw_data (actual LP change amount)
            let lp_delta = raw_data
                .get("leaguePointsDelta")
                .and_then(|v| v.as_i64())
                .or_else(|| raw_data.get("lpChange").and_then(|v| v.as_i64()))
                .unwrap_or(0);
            let tier = raw_data
                .get("tier")
                .and_then(|v| v.as_str())
                .unwrap_or("UNRANKED");
            let division = raw_data
                .get("division")
                .and_then(|v| v.as_str())
                .unwrap_or("");
            let lp_after = raw_data
                .get("leaguePoints")
                .and_then(|v| v.as_i64())
                .unwrap_or(0);
            let sign = if lp_delta >= 0 { "+" } else { "" };
            let rank = if division.is_empty() {
                tier.to_string()
            } else {
                format!("{} {}", tier, division)
            };
            format!(
                "LP Change: {}{} LP ({} → {} LP)",
                sign, lp_delta, rank, lp_after
            )
        }
        _ => "Unknown event".to_string(),
    }
}

// ============================================================================
// Player Metadata Structures (Runes, Summoner Spells, Items)
// ============================================================================

/// Rune slot information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuneSlot {
    /// Rune ID
    pub rune_id: u32,
    /// Slot index
    pub slot: u32,
}

/// Rune page configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RunePage {
    /// Primary rune style (keystone tree)
    pub primary_style_id: u32,
    /// Secondary rune style
    pub secondary_style_id: u32,
    /// Selected runes
    pub selected_perks: Vec<u32>,
    /// Stat modifiers (flex, offense, defense)
    #[serde(default)]
    pub stat_modifiers: Vec<u32>,
    /// Rune page name
    #[serde(default)]
    pub name: Option<String>,
    /// Whether this is the current page
    #[serde(default)]
    pub current: bool,
}

/// Summoner spell information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SummonerSpells {
    /// First summoner spell ID
    pub spell1_id: u32,
    /// Second summoner spell ID
    pub spell2_id: u32,
    /// First summoner spell name (resolved)
    #[serde(default)]
    pub spell1_name: Option<String>,
    /// Second summoner spell name (resolved)
    #[serde(default)]
    pub spell2_name: Option<String>,
}

/// Item information at game end
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemBuild {
    /// Final items (6 item slots)
    pub items: Vec<ItemInfo>,
    /// Trinket item
    #[serde(default)]
    pub trinket: Option<ItemInfo>,
}

/// Individual item information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemInfo {
    /// Item ID
    pub item_id: u32,
    /// Item name (resolved)
    #[serde(default)]
    pub name: Option<String>,
    /// Slot index (0-5 for main items, 6 for trinket)
    pub slot: u32,
}

/// Complete player metadata captured at game start
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PlayerGameMetadata {
    /// Player's PUUID
    #[serde(default)]
    pub puuid: Option<String>,
    /// Player's summoner name
    #[serde(default)]
    pub summoner_name: Option<String>,
    /// Champion ID
    #[serde(default)]
    pub champion_id: Option<u32>,
    /// Champion name
    #[serde(default)]
    pub champion_name: Option<String>,
    /// Skin ID
    #[serde(default)]
    pub skin_id: Option<u32>,
    /// Skin name
    #[serde(default)]
    pub skin_name: Option<String>,
    /// Team (100 = blue, 200 = red)
    #[serde(default)]
    pub team: Option<u32>,
    /// Selected rune page
    #[serde(default)]
    pub runes: Option<RunePage>,
    /// Summoner spells
    #[serde(default)]
    pub summoner_spells: Option<SummonerSpells>,
    /// Final item build (captured at game end)
    #[serde(default)]
    pub final_items: Option<ItemBuild>,
}

/// Player information for mapping puuid to summoner name
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlayerIdentity {
    /// Player's PUUID
    pub puuid: String,
    /// Player's summoner name
    pub summoner_name: String,
    /// Player's summoner ID
    #[serde(default)]
    pub summoner_id: Option<u64>,
    /// Player's champion name
    #[serde(default)]
    pub champion_name: Option<String>,
    /// Player's team (100 or 200)
    #[serde(default)]
    pub team: Option<u32>,
}

// ============================================================================
// GameFlow Session Data Structures
// ============================================================================

/// Full gameflow session data from /lol-gameflow/v1/session
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct GameflowSession {
    pub game_id: u64,
    pub game_name: String,
    pub is_custom_game: bool,
    pub password: String,
    pub player_champion_selections: Vec<PlayerChampionSelection>,
    pub queue: Option<QueueInfo>,
    pub spectator_key: Option<String>,
    pub spectators_allowed: bool,
    pub team_one: Vec<TeamMember>,
    pub team_two: Vec<TeamMember>,
    #[serde(default)]
    pub phase: Option<String>,
}

/// Player's champion selection info
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct PlayerChampionSelection {
    pub champion_id: u32,
    pub puuid: String,
    pub selected_skin_index: u32,
    pub spell1_id: u32,
    pub spell2_id: u32,
}

/// Queue information
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct QueueInfo {
    pub id: u32,
    pub name: String,
    pub short_name: Option<String>,
    pub description: String,
    pub detailed_description: Option<String>,
    pub game_mode: String,
    pub map_id: u32,
    pub category: String,
    #[serde(default)]
    pub is_ranked: bool,
    #[serde(default)]
    pub is_custom: bool,
    pub r#type: Option<String>,
    pub num_players_per_team: u32,
    pub maximum_participant_list_size: u32,
    pub minimum_participant_list_size: u32,
    pub champions_required_to_play: u32,
    #[serde(default)]
    pub are_free_champions_allowed: bool,
    pub queue_availability: Option<String>,
    pub spectator_enabled: Option<bool>,
}

/// Team member information
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TeamMember {
    pub champion_id: u32,
    pub last_selected_skin_index: u32,
    pub profile_icon_id: u32,
    pub puuid: String,
    pub selected_position: String,
    pub selected_role: String,
    pub summoner_id: u64,
    pub summoner_internal_name: String,
    pub summoner_name: String,
    #[serde(default)]
    pub team_owner: bool,
    pub team_participant_id: u32,
}

impl GameflowSession {
    /// Get a player's champion selection by puuid
    pub fn get_player_selection(&self, puuid: &str) -> Option<&PlayerChampionSelection> {
        self.player_champion_selections
            .iter()
            .find(|s| s.puuid == puuid)
    }

    /// Get a player's team member info by puuid (returns team member and team id)
    pub fn get_player_team_member(&self, puuid: &str) -> Option<(&TeamMember, u32)> {
        // Check team one
        for member in &self.team_one {
            if member.puuid == puuid {
                return Some((member, 100));
            }
        }
        // Check team two
        for member in &self.team_two {
            if member.puuid == puuid {
                return Some((member, 200));
            }
        }
        None
    }

    /// Get a player's champion ID by puuid
    pub fn get_champion_id(&self, puuid: &str) -> Option<u32> {
        self.get_player_selection(puuid).map(|s| s.champion_id)
    }

    /// Get a player's team (100 or 200) by puuid
    pub fn get_team(&self, puuid: &str) -> Option<u32> {
        self.get_player_team_member(puuid).map(|(_, team)| team)
    }

    /// Get a player's summoner name by puuid
    pub fn get_summoner_name(&self, puuid: &str) -> Option<&str> {
        self.get_player_team_member(puuid).and_then(|(member, _)| {
            if member.summoner_name.is_empty() {
                None
            } else {
                Some(member.summoner_name.as_str())
            }
        })
    }

    /// Get queue name
    pub fn queue_name(&self) -> Option<&str> {
        self.queue.as_ref().map(|q| q.name.as_str())
    }

    /// Get game mode
    pub fn game_mode(&self) -> Option<&str> {
        self.queue.as_ref().map(|q| q.game_mode.as_str())
    }

    /// Get map ID
    pub fn map_id(&self) -> Option<u32> {
        self.queue.as_ref().map(|q| q.map_id)
    }

    /// Get queue ID
    pub fn queue_id(&self) -> Option<u32> {
        self.queue.as_ref().map(|q| q.id)
    }
}

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

    #[test]
    fn test_event_type_constants() {
        assert_eq!(EVENT_TYPE_GAME_START, "game_start");
        assert_eq!(EVENT_TYPE_LP_CHANGE, "lp_change");
        assert_eq!(EVENT_TYPE_KILL, "kill");
    }

    #[test]
    fn test_describe_event_lp_change() {
        let raw = serde_json::json!({
            "lpChange": 22,
            "lpAfter": 75,
            "tier": "GOLD",
            "queueType": "RANKED_SOLO_5x5"
        });
        let desc = describe_event(EVENT_TYPE_LP_CHANGE, &raw);
        assert!(desc.contains("+22"));
        assert!(desc.contains("GOLD"));
    }

    #[test]
    fn test_describe_event_kill() {
        let raw = serde_json::json!({
            "KillerName": "Player1",
            "VictimName": "Player2"
        });
        let desc = describe_event(EVENT_TYPE_KILL, &raw);
        assert_eq!(desc, "Player1 killed Player2");
    }

    #[test]
    fn test_describe_event_phase_change() {
        // Phase change raw_data is just the phase string
        let raw = serde_json::json!("InProgress");
        let desc = describe_event(EVENT_TYPE_PHASE_CHANGE, &raw);
        assert_eq!(desc, "Phase changed to: InProgress");
    }
}
