use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

use crate::error::RlStatsError;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventEnvelope<T = Value> {
    #[serde(rename = "Event", alias = "event")]
    pub event: String,
    #[serde(rename = "Data", alias = "data")]
    pub data: T,
}

#[derive(Debug, Clone)]
pub enum StatsEvent {
    UpdateState(UpdateStateData),
    BallHit(BallHitData),
    ClockUpdatedSeconds(ClockUpdatedSecondsData),
    CountdownBegin(MatchOnlyData),
    CrossbarHit(CrossbarHitData),
    GoalReplayEnd(MatchOnlyData),
    GoalReplayStart(MatchOnlyData),
    GoalReplayWillEnd(MatchOnlyData),
    GoalScored(GoalScoredData),
    MatchCreated(MatchOnlyData),
    MatchInitialized(MatchOnlyData),
    MatchDestroyed(MatchOnlyData),
    MatchEnded(MatchEndedData),
    MatchPaused(MatchOnlyData),
    MatchUnpaused(MatchOnlyData),
    PodiumStart(MatchOnlyData),
    ReplayCreated(MatchOnlyData),
    RoundStarted(MatchOnlyData),
    StatfeedEvent(StatfeedEventData),
    Unknown(UnknownEvent),
}

#[derive(Debug, Clone)]
pub struct UnknownEvent {
    pub event: String,
    pub data: Value,
}

pub fn stats_event_name(event: &StatsEvent) -> &'static str {
    match event {
        StatsEvent::UpdateState(_) => "UpdateState",
        StatsEvent::BallHit(_) => "BallHit",
        StatsEvent::ClockUpdatedSeconds(_) => "ClockUpdatedSeconds",
        StatsEvent::CountdownBegin(_) => "CountdownBegin",
        StatsEvent::CrossbarHit(_) => "CrossbarHit",
        StatsEvent::GoalReplayEnd(_) => "GoalReplayEnd",
        StatsEvent::GoalReplayStart(_) => "GoalReplayStart",
        StatsEvent::GoalReplayWillEnd(_) => "GoalReplayWillEnd",
        StatsEvent::GoalScored(_) => "GoalScored",
        StatsEvent::MatchCreated(_) => "MatchCreated",
        StatsEvent::MatchInitialized(_) => "MatchInitialized",
        StatsEvent::MatchDestroyed(_) => "MatchDestroyed",
        StatsEvent::MatchEnded(_) => "MatchEnded",
        StatsEvent::MatchPaused(_) => "MatchPaused",
        StatsEvent::MatchUnpaused(_) => "MatchUnpaused",
        StatsEvent::PodiumStart(_) => "PodiumStart",
        StatsEvent::ReplayCreated(_) => "ReplayCreated",
        StatsEvent::RoundStarted(_) => "RoundStarted",
        StatsEvent::StatfeedEvent(_) => "StatfeedEvent",
        StatsEvent::Unknown(_) => "Unknown",
    }
}

pub fn stats_event_to_value(event: &StatsEvent) -> Result<Value, RlStatsError> {
    let value = match event {
        StatsEvent::UpdateState(data) => {
            json!({"event": "UpdateState", "data": data})
        }
        StatsEvent::BallHit(data) => {
            json!({"event": "BallHit", "data": data})
        }
        StatsEvent::ClockUpdatedSeconds(data) => {
            json!({"event": "ClockUpdatedSeconds", "data": data})
        }
        StatsEvent::CountdownBegin(data) => {
            json!({"event": "CountdownBegin", "data": data})
        }
        StatsEvent::CrossbarHit(data) => {
            json!({"event": "CrossbarHit", "data": data})
        }
        StatsEvent::GoalReplayEnd(data) => {
            json!({"event": "GoalReplayEnd", "data": data})
        }
        StatsEvent::GoalReplayStart(data) => {
            json!({"event": "GoalReplayStart", "data": data})
        }
        StatsEvent::GoalReplayWillEnd(data) => {
            json!({"event": "GoalReplayWillEnd", "data": data})
        }
        StatsEvent::GoalScored(data) => {
            json!({"event": "GoalScored", "data": data})
        }
        StatsEvent::MatchCreated(data) => {
            json!({"event": "MatchCreated", "data": data})
        }
        StatsEvent::MatchInitialized(data) => {
            json!({"event": "MatchInitialized", "data": data})
        }
        StatsEvent::MatchDestroyed(data) => {
            json!({"event": "MatchDestroyed", "data": data})
        }
        StatsEvent::MatchEnded(data) => {
            json!({"event": "MatchEnded", "data": data})
        }
        StatsEvent::MatchPaused(data) => {
            json!({"event": "MatchPaused", "data": data})
        }
        StatsEvent::MatchUnpaused(data) => {
            json!({"event": "MatchUnpaused", "data": data})
        }
        StatsEvent::PodiumStart(data) => {
            json!({"event": "PodiumStart", "data": data})
        }
        StatsEvent::ReplayCreated(data) => {
            json!({"event": "ReplayCreated", "data": data})
        }
        StatsEvent::RoundStarted(data) => {
            json!({"event": "RoundStarted", "data": data})
        }
        StatsEvent::StatfeedEvent(data) => {
            json!({"event": "StatfeedEvent", "data": data})
        }
        StatsEvent::Unknown(data) => json!({
            "event": data.event,
            "data": data.data,
        }),
    };

    Ok(value)
}

pub fn parse_stats_event(input: &str) -> Result<StatsEvent, RlStatsError> {
    let envelope: EventEnvelope<Value> = serde_json::from_str(input)?;
    parse_event_envelope(envelope)
}

pub fn parse_stats_event_value(
    value: Value,
) -> Result<StatsEvent, RlStatsError> {
    let envelope: EventEnvelope<Value> = serde_json::from_value(value)?;
    parse_event_envelope(envelope)
}

fn parse_event_envelope(
    envelope: EventEnvelope<Value>,
) -> Result<StatsEvent, RlStatsError> {
    let data = normalize_event_data(envelope.data)?;

    let event = match envelope.event.as_str() {
        "UpdateState" => StatsEvent::UpdateState(serde_json::from_value(data)?),
        "BallHit" => StatsEvent::BallHit(serde_json::from_value(data)?),
        "ClockUpdatedSeconds" => {
            StatsEvent::ClockUpdatedSeconds(serde_json::from_value(data)?)
        }
        "CountdownBegin" => {
            StatsEvent::CountdownBegin(serde_json::from_value(data)?)
        }
        "CrossbarHit" => StatsEvent::CrossbarHit(serde_json::from_value(data)?),
        "GoalReplayEnd" => {
            StatsEvent::GoalReplayEnd(serde_json::from_value(data)?)
        }
        "GoalReplayStart" => {
            StatsEvent::GoalReplayStart(serde_json::from_value(data)?)
        }
        "GoalReplayWillEnd" => {
            StatsEvent::GoalReplayWillEnd(serde_json::from_value(data)?)
        }
        "GoalScored" => StatsEvent::GoalScored(serde_json::from_value(data)?),
        "MatchCreated" => {
            StatsEvent::MatchCreated(serde_json::from_value(data)?)
        }
        "MatchInitialized" => {
            StatsEvent::MatchInitialized(serde_json::from_value(data)?)
        }
        "MatchDestroyed" => {
            StatsEvent::MatchDestroyed(serde_json::from_value(data)?)
        }
        "MatchEnded" => StatsEvent::MatchEnded(serde_json::from_value(data)?),
        "MatchPaused" => StatsEvent::MatchPaused(serde_json::from_value(data)?),
        "MatchUnpaused" => {
            StatsEvent::MatchUnpaused(serde_json::from_value(data)?)
        }
        "PodiumStart" => StatsEvent::PodiumStart(serde_json::from_value(data)?),
        "ReplayCreated" => {
            StatsEvent::ReplayCreated(serde_json::from_value(data)?)
        }
        "RoundStarted" => {
            StatsEvent::RoundStarted(serde_json::from_value(data)?)
        }
        "StatfeedEvent" => {
            StatsEvent::StatfeedEvent(serde_json::from_value(data)?)
        }
        _ => StatsEvent::Unknown(UnknownEvent {
            event: envelope.event,
            data,
        }),
    };

    Ok(event)
}

fn normalize_event_data(data: Value) -> Result<Value, RlStatsError> {
    match data {
        Value::String(raw) => {
            let parsed = serde_json::from_str::<Value>(&raw)?;
            Ok(parsed)
        }
        other => Ok(other),
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MatchOnlyData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Vector3 {
    #[serde(rename = "X", default)]
    pub x: f64,
    #[serde(rename = "Y", default)]
    pub y: f64,
    #[serde(rename = "Z", default)]
    pub z: f64,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlayerRef {
    #[serde(rename = "Name", default)]
    pub name: String,
    #[serde(rename = "Shortcut", default)]
    pub shortcut: i64,
    #[serde(rename = "TeamNum", default)]
    pub team_num: i64,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LastTouch {
    #[serde(rename = "Player", default)]
    pub player: PlayerRef,
    #[serde(rename = "Speed", default)]
    pub speed: f64,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BallHitBall {
    #[serde(rename = "PreHitSpeed", default)]
    pub pre_hit_speed: f64,
    #[serde(rename = "PostHitSpeed", default)]
    pub post_hit_speed: f64,
    #[serde(rename = "Location")]
    pub location: Vector3,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BallHitData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "Players", default)]
    pub players: Vec<PlayerRef>,
    #[serde(rename = "Ball", default)]
    pub ball: BallHitBall,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClockUpdatedSecondsData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "TimeSeconds", default)]
    pub time_seconds: i64,
    #[serde(rename = "bOvertime", default)]
    pub b_overtime: bool,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CrossbarHitData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "BallLocation")]
    pub ball_location: Vector3,
    #[serde(rename = "BallSpeed", default)]
    pub ball_speed: f64,
    #[serde(rename = "ImpactForce", default)]
    pub impact_force: f64,
    #[serde(rename = "BallLastTouch", default)]
    pub ball_last_touch: LastTouch,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GoalScoredData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "GoalSpeed", default)]
    pub goal_speed: f64,
    #[serde(rename = "GoalTime", default)]
    pub goal_time: f64,
    #[serde(rename = "ImpactLocation")]
    pub impact_location: Vector3,
    #[serde(rename = "Scorer", default)]
    pub scorer: PlayerRef,
    #[serde(rename = "Assister", default)]
    pub assister: Option<PlayerRef>,
    #[serde(rename = "BallLastTouch", default)]
    pub ball_last_touch: LastTouch,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MatchEndedData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "WinnerTeamNum", default)]
    pub winner_team_num: i64,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StatfeedEventData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "EventName", default)]
    pub event_name: String,
    #[serde(rename = "Type", default)]
    pub type_label: String,
    #[serde(rename = "MainTarget", default)]
    pub main_target: PlayerRef,
    #[serde(rename = "SecondaryTarget", default)]
    pub secondary_target: Option<PlayerRef>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TeamState {
    #[serde(rename = "Name", default)]
    pub name: Option<String>,
    #[serde(rename = "TeamNum", default)]
    pub team_num: Option<i64>,
    #[serde(rename = "Score", default)]
    pub score: Option<i64>,
    #[serde(rename = "ColorPrimary", default)]
    pub color_primary: Option<String>,
    #[serde(rename = "ColorSecondary", default)]
    pub color_secondary: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BallState {
    #[serde(rename = "Speed", default)]
    pub speed: Option<f64>,
    #[serde(rename = "TeamNum", default)]
    pub team_num: Option<i64>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateStateGame {
    #[serde(rename = "Teams", default)]
    pub teams: Vec<TeamState>,
    #[serde(rename = "TimeSeconds", default)]
    pub time_seconds: Option<i64>,
    #[serde(rename = "bOvertime", default)]
    pub b_overtime: Option<bool>,
    #[serde(rename = "Frame", default)]
    pub frame: Option<i64>,
    #[serde(rename = "Elapsed", default)]
    pub elapsed: Option<f64>,
    #[serde(rename = "Ball", default)]
    pub ball: Option<BallState>,
    #[serde(rename = "bReplay", default)]
    pub b_replay: Option<bool>,
    #[serde(rename = "bHasWinner", default)]
    pub b_has_winner: Option<bool>,
    #[serde(rename = "Winner", default)]
    pub winner: Option<String>,
    #[serde(rename = "Arena", default)]
    pub arena: Option<String>,
    #[serde(rename = "bHasTarget", default)]
    pub b_has_target: Option<bool>,
    #[serde(rename = "Target", default)]
    pub target: Option<PlayerRef>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateStatePlayer {
    #[serde(rename = "Name", default)]
    pub name: Option<String>,
    #[serde(rename = "PrimaryId", default)]
    pub primary_id: Option<String>,
    #[serde(rename = "Shortcut", default)]
    pub shortcut: Option<i64>,
    #[serde(rename = "TeamNum", default)]
    pub team_num: Option<i64>,
    #[serde(rename = "Score", default)]
    pub score: Option<i64>,
    #[serde(rename = "Goals", default)]
    pub goals: Option<i64>,
    #[serde(rename = "Shots", default)]
    pub shots: Option<i64>,
    #[serde(rename = "Assists", default)]
    pub assists: Option<i64>,
    #[serde(rename = "Saves", default)]
    pub saves: Option<i64>,
    #[serde(rename = "Touches", default)]
    pub touches: Option<i64>,
    #[serde(rename = "CarTouches", default)]
    pub car_touches: Option<i64>,
    #[serde(rename = "Demos", default)]
    pub demos: Option<i64>,
    #[serde(rename = "bHasCar", default)]
    pub b_has_car: Option<bool>,
    #[serde(rename = "Speed", default)]
    pub speed: Option<f64>,
    #[serde(rename = "Boost", default)]
    pub boost: Option<i64>,
    #[serde(rename = "bBoosting", default)]
    pub b_boosting: Option<bool>,
    #[serde(rename = "bOnGround", default)]
    pub b_on_ground: Option<bool>,
    #[serde(rename = "bOnWall", default)]
    pub b_on_wall: Option<bool>,
    #[serde(rename = "bPowersliding", default)]
    pub b_powersliding: Option<bool>,
    #[serde(rename = "bDemolished", default)]
    pub b_demolished: Option<bool>,
    #[serde(rename = "bSupersonic", default)]
    pub b_supersonic: Option<bool>,
    #[serde(rename = "Attacker", default)]
    pub attacker: Option<PlayerRef>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

impl UpdateStatePlayer {
    pub fn effective_speed(&self) -> Option<f64> {
        self.speed
            .or_else(|| {
                first_numeric_f64(
                    &self.extra,
                    &[
                        "speed",
                        "Speed",
                        "CarSpeed",
                        "carSpeed",
                        "car_speed",
                        "Velocity",
                        "velocity",
                        "Car.Speed",
                        "car.speed",
                    ],
                )
            })
            .or_else(|| {
                first_nested_numeric_f64(
                    &self.extra,
                    &[
                        "Car", "car", "CarData", "carData", "car_data",
                        "Vehicle", "vehicle",
                    ],
                    &[
                        "Speed",
                        "speed",
                        "CarSpeed",
                        "carSpeed",
                        "car_speed",
                        "Velocity",
                        "velocity",
                    ],
                )
            })
    }

    pub fn effective_boost(&self) -> Option<i64> {
        self.boost
            .or_else(|| {
                first_numeric_i64(
                    &self.extra,
                    &[
                        "boost",
                        "Boost",
                        "BoostAmount",
                        "boostAmount",
                        "boost_amount",
                        "Car.Boost",
                        "car.boost",
                    ],
                )
            })
            .or_else(|| {
                first_nested_numeric_i64(
                    &self.extra,
                    &[
                        "Car", "car", "CarData", "carData", "car_data",
                        "Vehicle", "vehicle",
                    ],
                    &[
                        "Boost",
                        "boost",
                        "BoostAmount",
                        "boostAmount",
                        "boost_amount",
                    ],
                )
            })
    }

    pub fn effective_boosting(&self) -> Option<bool> {
        self.b_boosting
            .or_else(|| {
                first_bool(
                    &self.extra,
                    &["bBoosting", "boosting", "isBoosting"],
                )
            })
            .or_else(|| {
                first_nested_bool(
                    &self.extra,
                    &[
                        "Car", "car", "CarData", "carData", "car_data",
                        "Vehicle", "vehicle",
                    ],
                    &["bBoosting", "boosting", "isBoosting"],
                )
            })
    }

    pub fn effective_supersonic(&self) -> Option<bool> {
        self.b_supersonic
            .or_else(|| {
                first_bool(
                    &self.extra,
                    &["bSupersonic", "supersonic", "isSupersonic"],
                )
            })
            .or_else(|| {
                first_nested_bool(
                    &self.extra,
                    &[
                        "Car", "car", "CarData", "carData", "car_data",
                        "Vehicle", "vehicle",
                    ],
                    &["bSupersonic", "supersonic", "isSupersonic"],
                )
            })
    }
}

fn first_numeric_f64(
    map: &HashMap<String, Value>,
    keys: &[&str],
) -> Option<f64> {
    keys.iter()
        .find_map(|key| map.get(*key).and_then(value_as_f64))
}

fn first_numeric_i64(
    map: &HashMap<String, Value>,
    keys: &[&str],
) -> Option<i64> {
    keys.iter()
        .find_map(|key| map.get(*key).and_then(value_as_i64))
}

fn first_nested_numeric_f64(
    map: &HashMap<String, Value>,
    container_keys: &[&str],
    value_keys: &[&str],
) -> Option<f64> {
    container_keys.iter().find_map(|container_key| {
        let Value::Object(object) = map.get(*container_key)? else {
            return None;
        };

        value_keys
            .iter()
            .find_map(|value_key| object.get(*value_key).and_then(value_as_f64))
    })
}

fn first_nested_numeric_i64(
    map: &HashMap<String, Value>,
    container_keys: &[&str],
    value_keys: &[&str],
) -> Option<i64> {
    container_keys.iter().find_map(|container_key| {
        let Value::Object(object) = map.get(*container_key)? else {
            return None;
        };

        value_keys
            .iter()
            .find_map(|value_key| object.get(*value_key).and_then(value_as_i64))
    })
}

fn first_bool(map: &HashMap<String, Value>, keys: &[&str]) -> Option<bool> {
    keys.iter()
        .find_map(|key| map.get(*key).and_then(value_as_bool))
}

fn first_nested_bool(
    map: &HashMap<String, Value>,
    container_keys: &[&str],
    value_keys: &[&str],
) -> Option<bool> {
    container_keys.iter().find_map(|container_key| {
        let Value::Object(object) = map.get(*container_key)? else {
            return None;
        };

        value_keys.iter().find_map(|value_key| {
            object.get(*value_key).and_then(value_as_bool)
        })
    })
}

fn value_as_f64(value: &Value) -> Option<f64> {
    match value {
        Value::Number(number) => number
            .as_f64()
            .or_else(|| number.as_i64().map(|v| v as f64)),
        Value::String(text) => text.parse::<f64>().ok(),
        _ => None,
    }
}

fn value_as_i64(value: &Value) -> Option<i64> {
    match value {
        Value::Number(number) => number
            .as_i64()
            .or_else(|| number.as_f64().map(|v| v.trunc() as i64)),
        Value::String(text) => text
            .parse::<i64>()
            .ok()
            .or_else(|| text.parse::<f64>().ok().map(|v| v.trunc() as i64)),
        _ => None,
    }
}

fn value_as_bool(value: &Value) -> Option<bool> {
    match value {
        Value::Bool(boolean) => Some(*boolean),
        Value::Number(number) => number.as_i64().map(|v| v != 0),
        Value::String(text) => {
            let normalized = text.trim().to_ascii_lowercase();
            match normalized.as_str() {
                "true" | "1" | "yes" | "y" | "on" => Some(true),
                "false" | "0" | "no" | "n" | "off" => Some(false),
                _ => None,
            }
        }
        _ => None,
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UpdateStateData {
    #[serde(rename = "MatchGuid", default)]
    pub match_guid: Option<String>,
    #[serde(rename = "Players", default)]
    pub players: Vec<UpdateStatePlayer>,
    #[serde(rename = "Game", default)]
    pub game: UpdateStateGame,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}
