use std::cmp::Ordering;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuildType {
    Unspecified,
    Alpha,
    Beta,
    ReleaseCandidate,
    Final,
    Patch,
}

impl BuildType {
    pub fn from_str_safe(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "" => BuildType::Unspecified,
            "a" => BuildType::Alpha,
            "b" => BuildType::Beta,
            "rc" => BuildType::ReleaseCandidate,
            "f" => BuildType::Final,
            "p" => BuildType::Patch,
            _ => BuildType::Unspecified,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnityVersion {
    pub major: i32,
    pub minor: i32,
    pub update: i32,
    pub build_type: BuildType,
    pub build_number: i32,
}

impl Default for UnityVersion {
    fn default() -> Self {
        Self {
            major: 0,
            minor: 0,
            update: 0,
            build_type: BuildType::Unspecified,
            build_number: 0,
        }
    }
}

impl PartialOrd for UnityVersion {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for UnityVersion {
    fn cmp(&self, other: &Self) -> Ordering {
        let mut cmp = self.major.cmp(&other.major);
        if cmp != Ordering::Equal { return cmp; }
        
        cmp = self.minor.cmp(&other.minor);
        if cmp != Ordering::Equal { return cmp; }
        
        cmp = self.update.cmp(&other.update);
        if cmp != Ordering::Equal { return cmp; }
        
        if self.build_type == BuildType::Unspecified || other.build_type == BuildType::Unspecified {
            return Ordering::Equal;
        }
        
        cmp = self.build_type.cmp(&other.build_type);
        if cmp != Ordering::Equal { return cmp; }
        
        self.build_number.cmp(&other.build_number)
    }
}

impl FromStr for UnityVersion {
    type Err = String;

    fn from_str(version_string: &str) -> Result<Self, Self::Err> {
        let mut parts = version_string.split('.');
        let major = parts.next().unwrap_or("").parse::<i32>().map_err(|_| "Invalid major version")?;
        let minor = parts.next().unwrap_or("").parse::<i32>().map_err(|_| "Invalid minor version")?;
        
        let mut update = 0;
        let mut build_type = BuildType::Unspecified;
        let mut build_number = 0;

        if let Some(rest) = parts.next() {
            let mut alpha_idx = None;
            for (i, c) in rest.char_indices() {
                if c.is_ascii_alphabetic() {
                    alpha_idx = Some(i);
                    break;
                }
            }

            if let Some(idx) = alpha_idx {
                update = rest[..idx].parse::<i32>().unwrap_or(0);
                
                let mut num_idx = None;
                for (i, c) in rest[idx..].char_indices() {
                    if c.is_ascii_digit() {
                        num_idx = Some(idx + i);
                        break;
                    }
                }
                
                if let Some(n_idx) = num_idx {
                    build_type = BuildType::from_str_safe(&rest[idx..n_idx]);
                    build_number = rest[n_idx..].parse::<i32>().unwrap_or(0);
                } else {
                    build_type = BuildType::from_str_safe(&rest[idx..]);
                }
            } else {
                update = rest.parse::<i32>().unwrap_or(0);
            }
        }

        Ok(UnityVersion {
            major,
            minor,
            update,
            build_type,
            build_number,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnityVersionRange {
    pub min: UnityVersion,
    pub max: Option<UnityVersion>,
}

impl UnityVersionRange {
    pub fn new(min: UnityVersion, max: Option<UnityVersion>) -> Self {
        Self { min, max }
    }

    pub fn contains(&self, version: &UnityVersion) -> bool {
        if version < &self.min {
            return false;
        }
        if let Some(max) = &self.max {
            if version > max {
                return false;
            }
        }
        true
    }

    pub fn intersect(&self, other: &UnityVersionRange) -> Option<UnityVersionRange> {
        let highest_low = if self.min > other.min { &self.min } else { &other.min };
        let lowest_high = match (&self.max, &other.max) {
            (Some(max1), Some(max2)) => if max1 < max2 { Some(max1) } else { Some(max2) },
            (Some(max1), None) => Some(max1),
            (None, Some(max2)) => Some(max2),
            (None, None) => None,
        };

        if let Some(lh) = lowest_high {
            if highest_low > lh {
                return None;
            }
        }

        Some(UnityVersionRange {
            min: highest_low.clone(),
            max: lowest_high.cloned(),
        })
    }

    pub fn from_filename(header_filename: &str) -> Self {
        let base_name = header_filename.trim_end_matches(".h");
        let parts: Vec<&str> = base_name.split('-').collect();
        
        let actual_parts = if parts.len() > 1 && parts[0].chars().any(|c| c.is_ascii_digit()) && parts[0].len() <= 4 {
            &parts[1..]
        } else {
            &parts[0..]
        };

        let min = UnityVersion::from_str(actual_parts[0]).unwrap_or_default();
        let max = if actual_parts.len() == 1 {
            Some(min.clone())
        } else if actual_parts.len() == 2 && !actual_parts[1].is_empty() {
            Some(UnityVersion::from_str(actual_parts[1]).unwrap_or_default())
        } else {
            None
        };

        UnityVersionRange { min, max }
    }
}
