use super::position::{GamePosition, GamePositionUtils};
use crate::core::Player;
use core::fmt;
use rand::RngExt;
use rand_chacha::ChaCha8Rng;
use rand_distr::{Distribution, Normal};
use serde::{Deserialize, Serialize};

pub type Skill = f32;

const NORMAL_AVG: f32 = 0.6;
const NORMAL_STD: f32 = 4.4;
const LEVEL_RANGE: f32 = 1.25;
pub const WEIGHT_MOD: f32 = 1.45;
pub const MIN_SKILL: f32 = 0.0;
pub const MAX_SKILL: f32 = 20.0;
pub const SKILL_NAMES: [&str; 20] = [
    "Quickness",
    "Vertical",
    "Strength",
    "Stamina",
    "Brawl",
    "Close",
    "Medium",
    "Long",
    "Steal",
    "Block",
    "Perimeter",
    "Interior",
    "Passing",
    "Handling",
    "Posting",
    "Rebounds",
    "Vision",
    "Aggression",
    "Intuition",
    "Charisma",
];

pub trait Rated {
    fn rating(&self) -> Skill;
    fn stars(&self) -> String {
        match self.rating() {
            0.0 => "☆☆☆☆☆".to_string(),
            x if x <= 2.0 => "½☆☆☆☆".to_string(),
            x if x <= 4.0 => "★☆☆☆☆".to_string(),
            x if x <= 6.0 => "★½☆☆☆".to_string(),
            x if x <= 8.0 => "★★☆☆☆".to_string(),
            x if x <= 10.0 => "★★½☆☆".to_string(),
            x if x <= 12.0 => "★★★☆☆".to_string(),
            x if x <= 14.0 => "★★★½☆".to_string(),
            x if x <= 16.0 => "★★★★☆".to_string(),
            x if x <= 18.0 => "★★★★½".to_string(),
            x if x <= 20.0 => "★★★★★".to_string(),
            _ => panic!("Invalid rating"),
        }
    }
}

impl Rated for f32 {
    fn rating(&self) -> Skill {
        *self as Skill
    }
}

impl Rated for u8 {
    fn rating(&self) -> Skill {
        *self as Skill
    }
}

pub trait GameSkill: fmt::Display + fmt::Debug {
    fn value(&self) -> u8 {
        self.bound() as u8
    }
    fn raw_value(&self) -> f32 {
        self.bound()
    }
    fn game_value(&self) -> i16 {
        self.bound() as i16
    }
    fn bound(&self) -> f32;
    fn normal_sample(&self, rng: &mut ChaCha8Rng) -> f32;
}

impl GameSkill for Skill {
    fn bound(&self) -> Skill {
        if self.is_nan() {
            log::error!("Bound skill is NaN: {self}.");
            return MIN_SKILL;
        }

        if self.is_infinite() {
            log::error!("Bound skill is Infinite: {self}.");
            return if self.is_sign_positive() {
                MAX_SKILL
            } else {
                MIN_SKILL
            };
        }

        self.clamp(MIN_SKILL, MAX_SKILL)
    }

    fn normal_sample(&self, rng: &mut ChaCha8Rng) -> Skill {
        Normal::new(NORMAL_AVG + self, NORMAL_STD)
            .unwrap()
            .sample(rng)
            .bound()
    }
}

pub trait RatedPlayers {
    fn sort_by_rating(self) -> Self;
}

impl RatedPlayers for Vec<&Player> {
    fn sort_by_rating(mut self) -> Self {
        self.sort_by(|a, b| {
            if a.rating() == b.rating() {
                b.average_skill()
                    .partial_cmp(&a.average_skill())
                    .expect("Skill value should exist")
            } else {
                b.rating()
                    .partial_cmp(&a.rating())
                    .expect("Skill value should exist")
            }
        });

        self
    }
}

#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq)]
pub struct Athletics {
    pub quickness: Skill,
    pub vertical: Skill,
    pub strength: Skill,
    pub stamina: Skill,
}

impl Athletics {
    pub fn for_position(position: GamePosition, rng: &mut ChaCha8Rng, base_level: f32) -> Self {
        let weights = position.weights();
        let level = base_level + rng.random_range(-LEVEL_RANGE..=LEVEL_RANGE);
        let quickness = (level + WEIGHT_MOD * weights[0]).normal_sample(rng);
        let vertical = (level + WEIGHT_MOD * weights[1]).normal_sample(rng);
        let strength = (level + WEIGHT_MOD * weights[2]).normal_sample(rng);
        let stamina = (level + WEIGHT_MOD * weights[3]).normal_sample(rng);
        Self {
            quickness,
            vertical,
            strength,
            stamina,
        }
    }
}

impl Rated for Athletics {
    fn rating(&self) -> Skill {
        (self.quickness + self.vertical + self.strength + self.stamina) / 4.0
    }
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Offense {
    pub brawl: Skill,
    pub close_range: Skill,
    pub medium_range: Skill,
    pub long_range: Skill,
}

impl Offense {
    pub fn for_position(position: GamePosition, rng: &mut ChaCha8Rng, base_level: f32) -> Self {
        let weights = position.weights();
        let level = base_level + rng.random_range(-LEVEL_RANGE..=LEVEL_RANGE);
        let brawl = (level + WEIGHT_MOD * weights[4]).normal_sample(rng);
        let close_range = (level + WEIGHT_MOD * weights[5]).normal_sample(rng);
        let medium_range = (level + WEIGHT_MOD * weights[6]).normal_sample(rng);
        let long_range = (level + WEIGHT_MOD * weights[7]).normal_sample(rng);
        Self {
            brawl,
            close_range,
            medium_range,
            long_range,
        }
    }
}

impl Rated for Offense {
    fn rating(&self) -> Skill {
        (self.brawl + self.close_range + self.medium_range + self.long_range) / 4.0
    }
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Defense {
    pub steal: Skill,
    pub block: Skill,
    pub perimeter_defense: Skill,
    pub interior_defense: Skill,
}

impl Defense {
    pub fn for_position(position: GamePosition, rng: &mut ChaCha8Rng, base_level: f32) -> Self {
        let weights = position.weights();
        let level = base_level + rng.random_range(-LEVEL_RANGE..=LEVEL_RANGE);
        let steal = (level + WEIGHT_MOD * weights[8]).normal_sample(rng);
        let block = (level + WEIGHT_MOD * weights[9]).normal_sample(rng);
        let perimeter_defense = (level + WEIGHT_MOD * weights[10]).normal_sample(rng);
        let interior_defense = (level + WEIGHT_MOD * weights[11]).normal_sample(rng);
        Self {
            steal,
            block,
            perimeter_defense,
            interior_defense,
        }
    }
}

impl Rated for Defense {
    fn rating(&self) -> Skill {
        (self.steal + self.block + self.perimeter_defense + self.interior_defense) / 4.0
    }
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Technical {
    pub passing: Skill,
    pub ball_handling: Skill,
    pub post_moves: Skill,
    pub rebounds: Skill,
}

impl Technical {
    pub fn for_position(position: GamePosition, rng: &mut ChaCha8Rng, base_level: f32) -> Self {
        let weights = position.weights();
        let level = base_level + rng.random_range(-LEVEL_RANGE..=LEVEL_RANGE);
        let passing = (level + WEIGHT_MOD * weights[12]).normal_sample(rng);
        let ball_handling = (level + WEIGHT_MOD * weights[13]).normal_sample(rng);
        let post_moves = (level + WEIGHT_MOD * weights[14]).normal_sample(rng);
        let rebounds = (level + WEIGHT_MOD * weights[15]).normal_sample(rng);
        Self {
            passing,
            ball_handling,
            post_moves,
            rebounds,
        }
    }
}

impl Rated for Technical {
    fn rating(&self) -> Skill {
        (self.passing + self.ball_handling + self.post_moves + self.rebounds) / 4.0
    }
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct Mental {
    pub vision: Skill,
    pub aggression: Skill,
    pub intuition: Skill,
    pub charisma: Skill,
}

impl Mental {
    pub fn for_position(position: GamePosition, rng: &mut ChaCha8Rng, base_level: f32) -> Self {
        let weights = position.weights();
        let level = base_level + rng.random_range(-LEVEL_RANGE..=LEVEL_RANGE);
        let vision = (level + WEIGHT_MOD * weights[16]).normal_sample(rng);
        let aggression = (level + WEIGHT_MOD * weights[17]).normal_sample(rng);
        let intuition = (level + WEIGHT_MOD * weights[18]).normal_sample(rng);
        let charisma = (level + WEIGHT_MOD * weights[19]).normal_sample(rng);
        Self {
            vision,
            aggression,
            intuition,
            charisma,
        }
    }
}

impl Rated for Mental {
    fn rating(&self) -> Skill {
        (self.vision + self.aggression + self.intuition + self.charisma) / 4.0
    }
}
