use std::{cmp::Ordering, collections::HashSet};

use rand::{
	seq::{IteratorRandom, SliceRandom},
	thread_rng,
};
use rayon::iter::{ParallelBridge, ParallelIterator};

use crate::{
	dictionnary::{self, gutenberg},
	game::{
		simulation::{wordle, wordle_buffs_for, wordle_inner},
		Info, InfoKind,
	},
};

use super::Solver;

#[derive(Debug)]
pub struct Grouping {
	dict: Vec<String>,
	candidates: Vec<String>,
}

impl Grouping {
	pub fn new<S: ToString>(dict: impl IntoIterator<Item = S>) -> Self {
		let dict = dict.into_iter().map(|s| s.to_string()).collect::<Vec<_>>();
		let candidates = dict.clone();
		Self { dict, candidates }
	}

	/// Gives a score to the word (lowest is best).
	fn rank_word_avg_groups(&self, word: &str) -> (usize, usize) {
		let mut groups = [0; 3];
		let mut buffs = wordle_buffs_for(word);
		for target in &self.candidates {
			wordle_inner(target, word, &mut buffs);
			let infos = &buffs.2;
			for i in infos {
				match i.kind {
					InfoKind::Abscent => groups[0] += 1,
					InfoKind::Elsewhere => groups[1] += 1,
					InfoKind::There => groups[2] += 1,
				}
			}
		}
		let min = groups.iter().min().unwrap();
		let max = groups.iter().max().unwrap();
		let there_count = groups[2];
		(max - min, there_count)
	}

	fn rank_word_biggest_group(&self, word: &str) -> (usize, usize) {
		let mut there_count = 0;
		let mut biggest = 0;
		let mut buffs = wordle_buffs_for(word);
		for target in &self.candidates {
			let mut groups = [0; 3];
			wordle_inner(target, word, &mut buffs);
			let infos = &buffs.2;
			for i in infos {
				match i.kind {
					InfoKind::Abscent => groups[0] += 1,
					InfoKind::Elsewhere => groups[1] += 1,
					InfoKind::There => (groups[2] += 1, there_count += 1).0,
				}
			}
			biggest = biggest.max(groups.into_iter().max().unwrap_or_default());
		}
		(biggest, there_count)
	}
}

impl Solver for Grouping {
	fn guess(&mut self) -> Option<String> {
		if self.candidates.len() == 1 {
			return self.candidates.first().cloned();
		}

		let scored = self
			.dict
			.iter()
			.take(100_000)
			.par_bridge()
			.map(|word| (word, self.rank_word_avg_groups(word)))
			.collect::<Vec<_>>();

		let best_score = scored.iter().map(|(_, (s, _))| *s).min()?;
		scored
			.into_iter()
			.filter(|(_, (s, _))| *s == best_score)
			.max_by_key(|(_, (_, t))| *t)
			.map(|(w, _)| w.to_string())
			.or_else(|| self.candidates.first().cloned())
	}

	fn learn(&mut self, infos: Vec<Info>) {
		let word = Info::to_word(infos.clone().into_iter());
		if let Some(index) = self
			.dict
			.iter()
			.enumerate()
			.find_map(|(i, w)| (*w == *word).then_some(i))
		{
			self.dict.remove(index);
		}
		self.candidates = self.candidates.drain(..).filter(|w| matches(w, &infos)).collect();
	}
}

pub fn matches(word: &str, infos: &[Info]) -> bool {
	let results = word
		.chars()
		.zip(infos)
		.map(|(w, info)| match (w, info.kind, info.character) {
			(w, InfoKind::There, c) if w == c => (None, None),
			(_, InfoKind::There, _) => (Some(false), None),
			(w, InfoKind::Elsewhere, c) if w == c => (Some(false), None),
			(w, _, _) => (None, Some((info, w))),
		});
	if let Some(conclusion) = results.clone().filter_map(|(c, _)| c).next() {
		return conclusion;
	}

	let remaining = results.filter_map(|(_, maybe)| maybe);
	let mut remaining_letters = remaining.clone().map(|(_, w)| Some(w)).collect::<Vec<_>>();
	let remaining_info = remaining.map(|(i, _)| i);
	for info in remaining_info {
		let found = remaining_letters
			.iter_mut()
			.find(|l| l.is_some_and(|l| l == info.character));
		match (info.kind, found) {
			(InfoKind::Abscent, Some(_)) => return false,
			(InfoKind::Abscent, _) => continue,
			(InfoKind::Elsewhere, None) => return false,
			(InfoKind::Elsewhere, Some(found)) => drop(found.take()),
			(InfoKind::There, _) => unreachable!(),
		}
	}

	true
}

#[test]
fn test_misc() {
	// 	let grouping = Grouping::new(["cocon", "coton"]);
	// 	assert!(grouping.rank_word("coton") > grouping.rank_word("abaca"));

	let grouping = Grouping::new(["abregee", "amorcee", "marquee", "assuree", "separee"]);
	dbg!(grouping.rank_word_avg_groups("volitif"));
	dbg!(grouping.rank_word_avg_groups("amorcee"));

	let mut gut = gutenberg(7);
	dbg!(gut.len());

	let mut grouping = Grouping::new(gut.into_iter().filter(|w| w.starts_with('j')));
	grouping.learn(vec![
		Info {
			kind: InfoKind::There,
			character: 'j',
		},
		Info {
			kind: InfoKind::Elsewhere,
			character: 'u',
		},
		Info {
			kind: InfoKind::Abscent,
			character: 'v',
		},
		Info {
			kind: InfoKind::Elsewhere,
			character: 'e',
		},
		Info {
			kind: InfoKind::Abscent,
			character: 'n',
		},
		Info {
			kind: InfoKind::Abscent,
			character: 'a',
		},
		Info {
			kind: InfoKind::Elsewhere,
			character: 't',
		},
	]);
	dbg!(grouping.rank_word_avg_groups("jouxtee"));
	dbg!(grouping.rank_word_avg_groups("jutions"));
	dbg!(grouping.rank_word_avg_groups("jaspine"));
	dbg!(grouping.guess());
}

#[test]
fn test_matches() {
	let cases = [
		("gargouille", "gargouille", "+....-...-", false),
		("gargouille", "guetteuses", "+....-...-", true),
		("grognement", "grognement", "+....+.-.-", false),
		("grognement", "guetteuses", "+....+.-.-", true),
		("gesticuler", "gesticuler", "+--+..+.+.", false),
		("gesticuler", "guetteuses", "+--+..+.+.", true),
		("guetteuses", "guetteuses", "++++++++++", true),
		("inoculeras", "inoculeras", "+-.-+.+-..", false),
		("inoculeras", "imprudence", "+-.-+.+-..", true),
		("implosions", "implosions", "+++.....-.", false),
		("implosions", "imprudence", "+++.....-.", true),
		("imprudents", "imprudents", "++++++++..", false),
		("imprudents", "imprudence", "++++++++..", true),
		("imprudence", "imprudence", "++++++++++", true),
		("cocon", "coton", "++.++", true),
		("coton", "coton", "+++++", true),
	];

	for (tried, word, infos, matches_) in cases {
		let infos_ = Info::from_printable(infos.chars(), tried.chars()).expect("Info deserialized correctly.");
		assert_eq!(
			matches(word, &infos_),
			matches_,
			"Asserts that matching '{}' against {} ({}) returns {}.\n{}\n{}\n{}",
			word,
			infos,
			tried,
			matches_,
			word,
			infos,
			tried,
		);
	}
}
