use crate::requirement::parser::parse_version_range;
use crate::version::RerVersion;
use core::fmt;
use lazy_static::lazy_static;
use regex::Regex;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use version_ranges::Ranges;

lazy_static! {
    static ref SEP_REGEZ_STR: Regex =
        Regex::new(r"[-@#]").expect("Can't compile SEP_REGEZ_STR regex");
    static ref SEP_REGEX: Regex = Regex::new(r"[-@#=<>]").expect("Can't compile SEP_REGEX regex");
}

/// # Requirement
///
/// ## Description
///
/// A requirement is the representation of the scope of range of a specific package.
/// Mainly it is use to represent a requierement in the list of `requieres` of a
/// package.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Requirement {
    pub name: String,
    pub range: Option<Ranges<RerVersion>>,
    weak_ref: bool,
    conflict: bool,
    sep: char,
    original_name: String,
}

impl Requirement {
    /// # new
    ///
    /// ## Description
    ///
    /// Create a new requirement from a string.
    ///
    /// ## Arguments
    ///
    /// * `s` - The string to parse
    fn parse_from_string(input_str: &str) -> Self {
        let original_name: String = input_str.to_string();
        let mut range = None;
        let mut weak_ref = false;
        let mut conflict = input_str.starts_with('!');
        let mut sep = '-';

        let mut input_str = input_str.to_string();
        if conflict {
            input_str.remove(0);
        } else if input_str.starts_with('~') {
            input_str.remove(0);
            conflict = true;
            weak_ref = true;
        }

        let name: String = if let Some(m) = SEP_REGEX.find(&input_str) {
            let mut req_str = input_str[m.start()..].to_string();
            if ['-', '@', '#'].contains(&req_str.chars().next().unwrap()) {
                sep = req_str.remove(0);
            }
            if req_str.contains('|') {
                let reqs: Vec<Ranges<RerVersion>> =
                    req_str.split('|').map(parse_version_range).collect();
                for req in reqs {
                    if range.is_none() {
                        range = Some(req);
                    } else {
                        range = Some(range.unwrap().union(&req));
                    }
                }
            } else {
                range = Some(parse_version_range(&req_str));
            }
            if conflict {
                range = Some(range.unwrap().complement());
            }
            input_str[..m.start()].to_string()
        } else if conflict {
            input_str
            // '~foo' equates to no effect, so range remains None
        } else {
            range = Some(Ranges::full());
            input_str
        };

        Requirement {
            name,
            range,
            weak_ref,
            conflict,
            sep,
            original_name,
        }
    }
    /// # get_pubgrub
    ///
    /// ## Description
    ///
    /// Get the name and the range of the requirement in the pubgrub format.
    /// Use mainly to add a dependency in the pubgrub solver.
    pub fn get_pubgrub(&self) -> (String, Ranges<RerVersion>) {
        (self.name.clone(), self.range.clone().unwrap())
    }
    /// # get_name
    ///
    /// ## Description
    ///
    /// Get the name of the requirement.
    ///
    /// ## Example
    ///
    /// ```rust
    /// use rer_version::Requirement;
    /// let a = Requirement::from("maya-1.2.3+<2.0.0");
    /// assert_eq!(a.get_name(), "maya");
    /// ```
    pub fn get_name(&self) -> &str {
        &self.name
    }
    /// # get_version_range
    ///
    /// ## Description
    ///
    /// Get the range of the requirement.
    pub fn get_version_range(&self) -> Option<Ranges<RerVersion>> {
        self.range.clone()
    }
    /// # merge
    ///
    /// ## Description
    ///
    /// Merge two requirements into one. If the requirements are not compatible, return None.
    pub fn merge(&self, other: &Self) -> Option<Self> {
        if self.name != other.name {
            return None; // cannot merge across object names
        }

        let merged_range = match (&self.range, &other.range) {
            (None, _) => other.range.clone(),
            (_, None) => self.range.clone(),
            (Some(a), Some(b)) => {
                if self.conflict {
                    if other.conflict {
                        Some(a.union(b)) // Merge conflicts by union
                    } else {
                        Some(Self::difference(b, a)) // Subtract self range from other
                    }
                } else if other.conflict {
                    Some(Self::difference(a, b)) // Subtract other range from self
                } else {
                    Some(a.intersection(b)) // Merge by intersection
                }
            }
        };

        merged_range.map(|range| Requirement {
            name: self.name.clone(),
            range: Some(range),
            weak_ref: self.weak_ref && other.weak_ref, // Assuming similar fields exist
            conflict: self.conflict && other.conflict, // Assuming similar logic applies
            sep: self.sep,                             // Assuming sep remains unchanged
            original_name: self.original_name.clone(), // Assuming a typo fix in field name
        })
    }
    fn difference(a: &Ranges<RerVersion>, b: &Ranges<RerVersion>) -> Ranges<RerVersion> {
        a.intersection(&b.complement())
    }
    /// # is_weak_ref
    ///
    /// ## Description
    ///
    /// Check if the requirement is a weak reference.
    pub fn is_weak_ref(&self) -> bool {
        self.weak_ref
    }
    fn from_pkg_and_range(name: String, range: Ranges<RerVersion>) -> Self {
        let original_name = format!("{}-{}", name, range);
        Requirement {
            name,
            range: Some(range),
            weak_ref: false,
            conflict: false,
            sep: '-',
            original_name,
        }
    }
}

#[test]
fn test_requierement() {
    let a = Requirement::from("voodoo-1");
    assert_eq!(a.name, "voodoo");
    let v: RerVersion = "1".try_into().unwrap();
    assert_eq!(a.range, Some(Ranges::between(v.clone(), v.bump())));
    let a = Requirement::from("voodoo-1.13.0|2.1.0");
    assert_eq!(a.name, "voodoo");
    let v: RerVersion = "1.13.0".try_into().unwrap();
    let v2: RerVersion = "2.1.0".try_into().unwrap();
    assert_eq!(
        a.range,
        Some(Ranges::between(v.clone(), v.bump()).union(&Ranges::between(v2.clone(), v2.bump())))
    );
}

impl fmt::Display for Requirement {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.original_name)
    }
}

impl Hash for Requirement {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.original_name.hash(state);
    }
}

impl From<&str> for Requirement {
    fn from(s: &str) -> Self {
        Requirement::parse_from_string(s)
    }
}
#[test]
fn test_to_string() {
    let a = Requirement::from("maya-1");
    assert_eq!(a.to_string(), "maya-1");
    let a = Requirement::from("maya");
    assert_eq!(a.to_string(), "maya");
    let a = Requirement::from("maya-1.2.3+<2.0.0");
    assert_eq!(a.to_string(), "maya-1.2.3+<2.0.0");
    let a = Requirement::from("maya-1.2.3+");
    assert_eq!(a.to_string(), "maya-1.2.3+");
}

#[test]
fn test_merge_requirement() {
    let a = Requirement::from("foo-1.2");
    let b = Requirement::from("~foo-1");
    let c = a.merge(&b).unwrap();
    assert_eq!(c.to_string(), "foo-1.2");
    let a = Requirement::from("foo-1.2");
    let b = Requirement::from("foo-1");
    let c = a.merge(&b).unwrap();
    assert_eq!(c.to_string(), "foo-1.2");
    let a = Requirement::from("foo-1.2");
    let b = Requirement::from("foo==1.2.2");
    let c = a.merge(&b).unwrap();
    let v_start: RerVersion = "1.2.2".try_into().unwrap();
    assert_eq!(c.get_version_range(), Some(Ranges::singleton(v_start)));
    let a = Requirement::from("foo-1.2");
    let b = Requirement::from("~foo-1");
    let c = a.merge(&b).unwrap();
    let v_start: RerVersion = "1.2".try_into().unwrap();
    let v_end: RerVersion = "1.2_".try_into().unwrap();
    assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
    let a = Requirement::from("foo-1.2");
    let b = Requirement::from("!foo-1");
    let c = a.merge(&b).unwrap();
    let v_start: RerVersion = "1.2".try_into().unwrap();
    let v_end: RerVersion = "1.2_".try_into().unwrap();
    println!("{:?}", c.get_version_range().unwrap().to_string());
    assert_eq!(c.get_version_range(), Some(Ranges::between(v_start, v_end)));
}

/// # Requirements
///
/// ## Description
///
/// A list of requirements.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Requirements(Vec<Requirement>);
impl Requirements {
    pub fn empty() -> Self {
        Requirements(Vec::new())
    }
    pub fn add(&mut self, requirement: Requirement) {
        self.0.push(requirement);
    }
    /// # from
    ///
    /// ## Description
    ///
    /// Create a list of requirements from a list of string.
    pub fn from(requirements: Vec<&str>) -> Self {
        let requirements = requirements
            .iter()
            .map(|x| Requirement::parse_from_string(x))
            .collect();
        Requirements(requirements)
    }
    /// # merge
    ///
    /// ## Description
    ///
    /// Merge all the requirements into one. If the requirements are not compatible, return None.
    pub fn merge(&self) -> Self {
        if self.0.len() <= 1 {
            return self.clone();
        }
        let mut requirements = self.0.clone();
        let mut i = 0;
        while i < requirements.len() {
            let mut j = i + 1;
            while j < requirements.len() {
                if let Some(req) = requirements[i].merge(&requirements[j]) {
                    requirements[i] = req;
                    requirements.remove(j);
                } else {
                    j += 1;
                }
            }
            i += 1;
        }
        Requirements(requirements)
    }
    pub fn extend(&mut self, other: &Self) {
        self.0.extend(other.0.clone());
    }
    pub fn switch(&mut self, other: &Self) {
        self.0.clone_from(&other.0)
    }
    /// # to_pubgrub
    ///
    /// ## Description
    ///
    /// Convert the requirements into a list of tuple of name and range.
    pub fn to_pubgrub(&self) -> Vec<(String, Ranges<RerVersion>)> {
        self.0.iter().map(|x| x.get_pubgrub()).collect()
    }
    /// # is_empty
    ///
    /// ## Description
    ///
    /// Check if the list of requirements is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
    pub fn split_weak_ref(&self) -> (Self, Self) {
        let mut weak_ref = Vec::new();
        let mut strong_ref = Vec::new();
        for req in &self.0 {
            if req.is_weak_ref() {
                weak_ref.push(req.clone());
            } else {
                strong_ref.push(req.clone());
            }
        }
        (Requirements(weak_ref), Requirements(strong_ref))
    }
    /// # split_conflict
    ///
    /// ## Description
    ///
    /// Split the requirements into two lists. One without the conflict and one with the conflict.
    ///
    /// ## Returns
    ///
    /// A tuple of two lists of requirements. `Requirements(no_conflict), Requirements(conflict)`
    pub fn split_conflict(&self) -> (Self, Self) {
        let mut conflict = Vec::new();
        let mut no_conflict = Vec::new();
        for req in &self.0 {
            if req.conflict {
                conflict.push(req.clone());
            } else {
                no_conflict.push(req.clone());
            }
        }
        (Requirements(no_conflict), Requirements(conflict))
    }
    pub fn reduced(
        &mut self,
        package_name: &String,
        range: &Ranges<RerVersion>,
    ) -> (Ranges<RerVersion>, Self) {
        let new_req = Requirement::from_pkg_and_range(package_name.clone(), range.clone());
        let requierements: Requirements = Requirements(vec![new_req]);
        self.extend(&requierements);
        let n = self.merge();
        let (no_conflict, _conflict) = n.split_conflict();
        let range = no_conflict
            .0
            .iter()
            .find(|x| x.name == *package_name)
            .unwrap()
            .range
            .clone()
            .unwrap();
        (range, n)
    }
}

impl fmt::Display for Requirements {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut first = true;
        for req in &self.0 {
            if first {
                first = false;
            } else {
                write!(f, ", ")?;
            }
            write!(f, "{}", req)?;
        }
        Ok(())
    }
}

impl Iterator for Requirements {
    type Item = Requirement;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.pop()
    }
}

#[test]
fn test_merge() {
    let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
    let requirements = Requirements::from(requirements_str);
    let requirements = requirements.merge();
    assert_eq!(requirements.0.len(), 2);
    assert_eq!(requirements.0[0].name, "foo");
}

#[test]
fn test_switch() {
    let requirements_str = vec!["foo-1.2", "bah-3", "~foo-1"];
    let mut requirements = Requirements::from(requirements_str);
    let requirements_str2 = vec!["foo-1.5", "bah-4", "~foo-6", "~toto-6"];
    let requirements2 = Requirements::from(requirements_str2);
    requirements.switch(&requirements2);
    assert_eq!(requirements, requirements2);
}

#[test]
fn test_reduce() {
    let requirements_str = vec!["~foo-1.0.5"];
    let mut requirements = Requirements::from(requirements_str);
    let v: RerVersion = "1".try_into().unwrap();
    let (range, _toto) =
        requirements.reduced(&"foo".to_string(), &Ranges::between(v.clone(), v.bump()));
    let v_req: RerVersion = "1.0.5".try_into().unwrap();
    let req_range: Ranges<RerVersion> = Ranges::between(v_req.clone(), v_req.bump());
    assert_eq!(range, req_range);
}
