//! Conflict and obsolete constraint processing and inversion
//!
//! This module handles the processing of package conflicts and obsoletes,
//! converting them into resolvo constraints. Key features:
//! - Constraint inversion for conflicts/obsoletes (resolvo forbids what doesn't match)
//! - Self-version constraint normalization (RPM format specific)
//! - Self-contradictory constraint filtering to prevent solver panics

use std::borrow::Cow;
use resolvo::{ConditionalRequirement, KnownDependencies};
use resolvo::Interner;

use crate::package_cache;
use crate::parse_requires::VersionConstraint;
use crate::provides::check_provider_satisfies_constraints;

impl crate::resolve::provider::GenericDependencyProvider {

    /// Process conflicts or obsoletes from a package
    ///
    /// Both conflicts and obsoletes prevent installation of matching packages and need constraint
    /// inversion because resolvo's constraint mechanism forbids packages that DON'T match the
    /// constraint. So if package A conflicts with or obsoletes b>1, we need to create a constraint
    /// b<=1, which will cause resolvo to forbid packages that don't match b<=1 (i.e., b version 2).
    pub fn process_conflicts_or_obsoletes(
        &self,
        dependency_strings: &[String],
        dep_type: &str,
        known_deps: &mut KnownDependencies,
        package: &crate::models::Package,
        pkgkey: &str,
    ) {
        for dep_str in dependency_strings {
            if let Ok(mut and_depends) = crate::parse_requires::parse_requires(self.format, dep_str) {
                // Normalize constraints: if we see "cap <= pkg.version" where pkg.version matches
                // the package's own version, change it to "cap < pkg.version" to avoid self-conflicts.
                // This is a common packaging mistake where packages obsolete capabilities at their
                // own version, which after inversion becomes "cap > pkg.version", conflicting with
                // same-version providers.
                self.fixup_self_version_constraints(&mut and_depends, &package.version);

                // Always invert constraints for conflicts and obsoletes
                let inverted_and_depends = self.invert_constraints_for_conflicts(&and_depends);

                // Filter out constraints that would conflict with the package's own provides
                let filtered_and_depends = self.filter_self_contradictory_constraints(
                    &inverted_and_depends,
                    pkgkey,
                );

                match self.convert_and_depends_to_requirements(&filtered_and_depends) {
                    Ok(reqs) => {
                        self.add_requirements_to_constraints(reqs, known_deps);
                    }
                    Err(e) => {
                        log::warn!(
                            "[RESOLVO] Failed to convert {} '{}': {}",
                            dep_type,
                            dep_str,
                            e
                        );
                    }
                }
            }
        }
    }

    /// Filter out constraints that would conflict with the package's own provides
    /// This prevents self-contradictory constraints that can cause resolvo to panic
    ///
    /// # Example
    ///
    /// Consider a package `tcl` that:
    /// - Provides: `tcl-tcldict = 8.6.14`
    /// - Obsoletes: `tcl-tcldict<=8.6.14`
    ///
    /// When processing obsoletes, the constraint `tcl-tcldict<=8.6.14` gets inverted to
    /// `tcl-tcldict>8.6.14`. However, the package itself provides `tcl-tcldict = 8.6.14`,
    /// which does NOT satisfy `>8.6.14`. This creates a self-contradictory constraint:
    /// the package requires `tcl-tcldict>8.6.14` but provides `tcl-tcldict = 8.6.14`.
    ///
    /// This function detects such cases and filters out the self-contradictory constraint,
    /// preventing resolvo from panicking with "watched_literals[0] != watched_literals[1]".
    fn filter_self_contradictory_constraints<'a>(
        &self,
        and_depends: &'a crate::parse_requires::AndDepends,
        pkgkey: &str,
    ) -> Cow<'a, crate::parse_requires::AndDepends> {
        use crate::parse_requires::PkgDepend;

        // Apply self-contradictory constraint filtering for all distros
        // This prevents packages from creating constraints that conflict with their own provides
        // which would cause resolution failures or solver panics.

        let filtered = and_depends
            .iter()
            .map(|or_depends| {
                or_depends
                    .iter()
                    .filter_map(|pkg_dep| {
                        let capability = &pkg_dep.capability;

                        // First check: if the package name itself matches the capability,
                        // or if the package provides the capability, this is a self-constraint
                        // that would create duplicate watched literals.
                        // Example: package `mesa-dri-drivers` listing a conflict/obsolete
                        // `mesa-dri-drivers` (possibly via inversion), which would require the
                        // solver to forbid the package from co-existing with itself.
                        // Example: package `texlive-plain-generic` provides `tex4ht` and also
                        // conflicts with `tex4ht`, which is a self-constraint.
                        let is_self_constraint = if let Ok(pkgname) = crate::package::pkgkey2pkgname(pkgkey) {
                            pkgname == *capability
                        } else {
                            false
                        } || self.package_provides_capability(pkgkey, capability);

                        if is_self_constraint {
                            log::debug!(
                                "[RESOLVO] Skipping self-constraint: package {} constrains itself by name/provide '{}'",
                                pkgkey,
                                capability
                            );
                            return None;
                        }

                        // Check if this package provides the capability
                        if !self.package_provides_capability(pkgkey, capability) {
                            // Package doesn't provide this capability, so the constraint is valid
                            return Some(pkg_dep.clone());
                        }

                        // Package provides this capability - check if the constraint would conflict
                        // with what the package provides
                        if pkg_dep.constraints.is_empty() {
                            // No version constraints, so this would conflict (package provides it)
                            log::debug!(
                                "[RESOLVO] Skipping self-contradictory constraint: package {} provides '{}' without version constraints",
                                pkgkey,
                                capability
                            );
                            return None;
                        }

                        // Filter out IfInstall constraints for version checking
                        let non_conditional_constraints: Vec<VersionConstraint> =
                            pkg_dep.constraints
                                .iter()
                                .filter(|c| c.operator != crate::parse_requires::Operator::IfInstall)
                                .cloned()
                                .collect();

                        if non_conditional_constraints.is_empty() {
                            // Only conditional constraints, so this is valid
                            return Some(pkg_dep.clone());
                        }

                        // Load the provider package to check its version
                        let provider_pkg = match package_cache::load_package_info(pkgkey) {
                            Ok(pkg) => pkg,
                            Err(_) => return Some(pkg_dep.clone()), // Can't load package, allow constraint
                        };

                        // Check if the package's provided version satisfies the constraints
                        match check_provider_satisfies_constraints(
                            &provider_pkg,
                            capability,
                            &non_conditional_constraints,
                            self.format,
                        ) {
                            Ok(true) => {
                                // Package provides a version that satisfies the constraint
                                // This is valid - the constraint is not self-contradictory
                                Some(pkg_dep.clone())
                            }
                            Ok(false) => {
                                // Package provides a version that doesn't satisfy the constraint
                                // This would create a self-contradictory constraint - skip it
                                log::debug!(
                                    "[RESOLVO] Skipping self-contradictory constraint: package {} provides '{}' with version that doesn't satisfy constraints {:?}",
                                    pkgkey,
                                    capability,
                                    non_conditional_constraints
                                );
                                None
                            }
                            Err(e) => {
                                log::warn!(
                                    "[RESOLVO] Error checking constraint for {}: {}, allowing constraint",
                                    capability,
                                    e
                                );
                                Some(pkg_dep.clone()) // On error, allow it
                            }
                        }
                    })
                    .collect()
            })
            .filter(|or_depends: &Vec<PkgDepend>| !or_depends.is_empty()) // Remove empty OR groups
            .collect();

        Cow::Owned(filtered)
    }

    /// Normalize constraints in conflicts/obsoletes to avoid self-conflicts (RPM format only)
    ///
    /// If a package has conflicts/obsoletes like "cap <= pkg.version" where pkg.version matches
    /// the package's own version, automatically change it to "cap < pkg.version". This prevents
    /// self-contradictory constraints after inversion (which would become "cap > pkg.version").
    ///
    /// This normalization is only applied to RPM format packages, as this is where this packaging
    /// mistake commonly occurs.
    fn fixup_self_version_constraints(
        &self,
        and_depends: &mut crate::parse_requires::AndDepends,
        package_version: &str,
    ) {
        // Only apply this fix to RPM format
        if self.format != crate::models::PackageFormat::Rpm {
            return;
        }

        use crate::parse_requires::Operator;
        use crate::parse_version::PackageVersion;

        let pkg_upstream = PackageVersion::parse(package_version)
            .ok()
            .map(|v| v.upstream);

        for or_depends in and_depends.iter_mut() {
            for pkg_dep in or_depends.iter_mut() {
                for constraint in pkg_dep.constraints.iter_mut() {
                    // Check if this is a "<= package_version" constraint
                    // If so, change it to "< package_version" to avoid self-conflicts after inversion
                    // Also handle cases where the constraint operand matches the upstream version
                    // even if the package version has epoch prefix and release part.
                    // Example: package version "1:8.6.14-1.oe2403sp1" vs constraint "8.6.14"
                    let matches = constraint.operator == Operator::VersionLessThanEqual
                        && (constraint.operand == package_version
                            || (pkg_upstream.is_some()
                                && PackageVersion::parse(&constraint.operand)
                                    .ok()
                                    .map(|v| v.upstream)
                                    == pkg_upstream));

                    if matches {
                        log::debug!(
                            "[RESOLVO] Normalizing self-version constraint (RPM): changing '{} <= {}' to '{} < {}' to avoid self-conflict",
                            pkg_dep.capability,
                            constraint.operand,
                            pkg_dep.capability,
                            constraint.operand
                        );
                        // Change <= to <
                        constraint.operator = Operator::VersionLessThan;
                    }
                }
            }
        }
    }

    /// Invert constraints for conflicts/obsoletes
    ///
    /// Resolvo's constraint mechanism forbids packages that DON'T match the constraint.
    /// So if we want to forbid packages matching "b>1", we need to create a constraint "b<=1",
    /// which will cause resolvo to forbid packages that don't match "b<=1" (i.e., b version 2).
    fn invert_constraints_for_conflicts(
        &self,
        and_depends: &crate::parse_requires::AndDepends,
    ) -> crate::parse_requires::AndDepends {
        use crate::parse_requires::{PkgDepend, VersionConstraint};

        and_depends
            .iter()
            .map(|or_depends| {
                or_depends
                    .iter()
                    .map(|pkg_dep| {
                        let inverted_constraints: Vec<VersionConstraint> =
                            pkg_dep
                                .constraints
                                .iter()
                                .map(|c| self.invert_constraint(c))
                                .collect();
                        PkgDepend {
                            capability: pkg_dep.capability.clone(),
                            constraints: inverted_constraints,
                        }
                    })
                    .collect()
            })
            .collect()
    }

    /// Invert a single constraint operator
    ///
    /// Inverts the operator so that packages matching the original constraint will NOT match
    /// the inverted constraint, and vice versa.
    fn invert_constraint(
        &self,
        constraint: &VersionConstraint,
    ) -> VersionConstraint {
        use crate::parse_requires::Operator;

        let inverted_op = match constraint.operator {
            Operator::VersionGreaterThan => Operator::VersionLessThanEqual,
            Operator::VersionGreaterThanEqual => Operator::VersionLessThan,
            Operator::VersionLessThan => Operator::VersionGreaterThanEqual,
            Operator::VersionLessThanEqual => Operator::VersionGreaterThan,
            Operator::VersionEqual => Operator::VersionNotEqual,
            _ => constraint.operator.clone(), // Keep other operators as-is for now
        };

        VersionConstraint {
            operator: inverted_op,
            operand: constraint.operand.clone(),
        }
    }

    /// Add requirements as constraints to known_deps
    fn add_requirements_to_constraints(
        &self,
        reqs: Vec<ConditionalRequirement>,
        known_deps: &mut KnownDependencies,
    ) {
        for req in reqs {
            match req.requirement {
                resolvo::Requirement::Single(version_set_id) => {
                    known_deps.constrains.push(version_set_id);
                }
                resolvo::Requirement::Union(union_id) => {
                    // Add all version sets in the union
                    for version_set_id in self.version_sets_in_union(union_id) {
                        known_deps.constrains.push(version_set_id);
                    }
                }
            }
        }
    }

}