use std::{collections::BTreeSet, collections::HashMap, path::PathBuf, sync::LazyLock};

use miette::Diagnostic;
use once_cell::unsync::OnceCell;
use pixi_build_backend::generated_recipe::MetadataProvider;
use rattler_conda_types::{ParseVersionError, Version};
use std::str::FromStr;

/// CRAN to SPDX license mapping loaded from JSON file.
/// This mapping is generated by `cran_license_mapper.py` and covers 99.9% of CRAN packages.
static CRAN_SPDX_MAP: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
    let json_str = include_str!("../license_mapping/cran_spdx_licenses.json");
    serde_json::from_str(json_str).expect("Failed to parse cran_spdx_licenses.json")
});

/// R packages that are built into r-base and should not be listed as separate dependencies.
///
/// This includes:
/// - Base packages (14): ship with every R installation
/// - Recommended packages (15): included in all binary distributions of R
///
/// See: <https://cran.r-project.org/doc/FAQ/R-FAQ.html>
pub const R_BUILTIN_PACKAGES: &[&str] = &[
    // Base packages (Priority: base)
    "base",
    "compiler",
    "datasets",
    "graphics",
    "grDevices",
    "grid",
    "methods",
    "parallel",
    "splines",
    "stats",
    "stats4",
    "tcltk",
    "tools",
    "utils",
    // Recommended packages (Priority: recommended)
    // These are included in all binary distributions of R
    "KernSmooth",
    "MASS",
    "Matrix",
    "boot",
    "class",
    "cluster",
    "codetools",
    "foreign",
    "lattice",
    "mgcv",
    "nlme",
    "nnet",
    "rpart",
    "spatial",
    "survival",
];

/// Check if an R package is built into r-base
pub fn is_builtin_package(name: &str) -> bool {
    R_BUILTIN_PACKAGES
        .iter()
        .any(|&pkg| pkg.eq_ignore_ascii_case(name))
}

/// Parsed R license expression
#[derive(Debug, Clone, PartialEq)]
pub struct ParsedLicense {
    /// The license identifier(s) (e.g., "MIT", "GPL-3", "GPL-2 | GPL-3")
    pub license: Option<String>,
    /// The license file path if specified (e.g., "LICENSE", "LICENCE")
    pub license_file: Option<String>,
}

/// Parse R DESCRIPTION license field
///
/// R packages use various license formats:
/// - "MIT + file LICENSE" -> license: MIT, file: LICENSE
/// - "GPL-3" -> license: GPL-3, file: None
/// - "GPL (>= 2)" -> license: GPL (>= 2), file: None
/// - "BSD_3_clause + file LICENSE" -> license: BSD-3-Clause, file: LICENSE
/// - "file LICENSE" -> license: None, file: LICENSE
/// - "GPL-2 | GPL-3" -> license: GPL-2 | GPL-3, file: None
pub fn parse_r_license(license_str: &str) -> ParsedLicense {
    let license_str = license_str.trim();

    // Check for "file" keyword
    if let Some(file_pos) = license_str.to_lowercase().find(" + file ") {
        // Format: "LICENSE + file FILENAME"
        let license_part = license_str[..file_pos].trim();
        let file_part = license_str[file_pos + 8..].trim(); // Skip " + file "

        ParsedLicense {
            license: if license_part.is_empty() {
                None
            } else {
                Some(normalize_license_name(license_part))
            },
            license_file: Some(file_part.to_string()),
        }
    } else if license_str.to_lowercase().starts_with("file ") {
        // Format: "file FILENAME"
        let file_part = license_str[5..].trim();
        ParsedLicense {
            license: None,
            license_file: Some(file_part.to_string()),
        }
    } else {
        // No file specified, just the license
        ParsedLicense {
            license: Some(normalize_license_name(license_str)),
            license_file: None,
        }
    }
}

/// Normalize R license names to standard SPDX identifiers where possible.
///
/// This uses the mapping from `cran_spdx_licenses.json`, which is generated
/// by `cran_license_mapper.py` and covers 99.9% of CRAN packages.
/// See: <https://spdx.org/licenses/>
fn normalize_license_name(license: &str) -> String {
    let license = license.trim();

    // Look up in the CRAN to SPDX mapping, fall back to original if not found
    CRAN_SPDX_MAP
        .get(license)
        .cloned()
        .unwrap_or_else(|| license.to_string())
}

/// Parsed R package dependency
#[derive(Debug, Clone, PartialEq)]
pub struct RDependency {
    /// Package name (e.g., "curl", "jsonlite")
    pub name: String,
    /// Optional version constraint (e.g., ">= 1.5", "(>= 2.1.3)")
    pub version: Option<String>,
}

/// Parse R dependency list from DESCRIPTION fields like Imports, Depends, Suggests
///
/// R dependencies are comma-separated with optional version constraints in parentheses:
/// - "curl, jsonlite, magrittr (>= 1.5)"
/// - "R (>= 4.1.0)"
/// - "testthat (>= 3.0.0), xml2, crul"
pub fn parse_r_dependencies(deps_str: &str) -> Vec<RDependency> {
    let mut dependencies = Vec::new();

    for dep in deps_str.split(',') {
        let dep = dep.trim();
        if dep.is_empty() {
            continue;
        }

        // Check for version constraint in parentheses
        if let Some(paren_pos) = dep.find('(') {
            let name = dep[..paren_pos].trim().to_string();
            let version_part = dep[paren_pos..].trim();

            // Extract version constraint (remove parentheses)
            let version = if version_part.starts_with('(') && version_part.ends_with(')') {
                Some(version_part[1..version_part.len() - 1].trim().to_string())
            } else {
                Some(version_part.to_string())
            };

            dependencies.push(RDependency { name, version });
        } else {
            // No version constraint
            dependencies.push(RDependency {
                name: dep.to_string(),
                version: None,
            });
        }
    }

    dependencies
}

/// Convert R package name to conda package name
///
/// R packages in conda typically use lowercase with 'r-' prefix:
/// - "curl" -> "r-curl"
/// - "jsonlite" -> "r-jsonlite"
/// - "R6" -> "r-r6"
pub fn r_package_to_conda(name: &str) -> String {
    // Special case: "R" refers to the R language itself, which is r-base
    if name == "R" {
        return "r-base".to_string();
    }

    format!("r-{}", name.to_lowercase())
}

/// Convert R version constraint to conda version constraint
///
/// R uses slightly different syntax:
/// - ">= 1.5" -> ">=1.5"
/// - "(>= 2.1.3)" -> ">=2.1.3"
pub fn r_version_to_conda(version: &str) -> String {
    version.trim().replace(['(', ')', ' '], "")
}

#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum MetadataError {
    #[error("failed to parse version from DESCRIPTION: {0}")]
    ParseVersion(#[from] ParseVersionError),
    #[error(transparent)]
    Io(#[from] std::io::Error),
}

/// Parsed DESCRIPTION file data
#[derive(Debug, Clone, Default)]
struct DescriptionData {
    package: Option<String>,
    version: Option<String>,
    title: Option<String>,
    description: Option<String>,
    license: Option<String>,
    url: Option<String>,
    bug_reports: Option<String>,
    #[allow(dead_code)]
    maintainer: Option<String>,
    linking_to: Option<String>,
    depends: Option<String>,
    imports: Option<String>,
}

/// MetadataProvider implementation for R DESCRIPTION files
pub struct DescriptionMetadataProvider {
    manifest_root: PathBuf,
    description_data: OnceCell<DescriptionData>,
}

impl DescriptionMetadataProvider {
    pub fn new(manifest_root: impl Into<PathBuf>) -> Self {
        Self {
            manifest_root: manifest_root.into(),
            description_data: OnceCell::default(),
        }
    }

    /// Parse DESCRIPTION file in DCF (Debian Control File) format
    fn parse_description(content: &str) -> Result<DescriptionData, MetadataError> {
        let mut data = DescriptionData::default();

        let mut current_key: Option<String> = None;
        let mut current_value = String::new();

        for line in content.lines() {
            if line.is_empty() {
                continue;
            }

            // Continuation line (starts with whitespace)
            if line.starts_with(char::is_whitespace) {
                if !current_value.is_empty() {
                    current_value.push(' ');
                }
                current_value.push_str(line.trim());
            } else if let Some(colon_pos) = line.find(':') {
                // Save previous key-value pair
                if let Some(key) = current_key.take() {
                    Self::store_field(&mut data, &key, &current_value);
                }

                // Start new key-value pair
                let key = line[..colon_pos].trim().to_string();
                current_value = line[colon_pos + 1..].trim().to_string();
                current_key = Some(key);
            }
        }

        // Store final key-value pair
        if let Some(key) = current_key {
            Self::store_field(&mut data, &key, &current_value);
        }

        Ok(data)
    }

    fn store_field(data: &mut DescriptionData, key: &str, value: &str) {
        let value = value.trim().to_string();
        if value.is_empty() {
            return;
        }

        match key {
            "Package" => data.package = Some(value),
            "Version" => data.version = Some(value),
            "Title" => data.title = Some(value),
            "Description" => data.description = Some(value),
            "License" => data.license = Some(value),
            "URL" => data.url = Some(value),
            "BugReports" => data.bug_reports = Some(value),
            "Maintainer" => data.maintainer = Some(value),
            "LinkingTo" => data.linking_to = Some(value),
            "Depends" => data.depends = Some(value),
            "Imports" => data.imports = Some(value),
            _ => {}
        }
    }

    fn ensure_data(&self) -> Result<&DescriptionData, MetadataError> {
        self.description_data.get_or_try_init(|| {
            let description_path = self.manifest_root.join("DESCRIPTION");
            let content = fs_err::read_to_string(&description_path)?;
            Self::parse_description(&content)
        })
    }

    /// Check if package has LinkingTo dependencies (indicates C++ code)
    pub fn has_linking_to(&self) -> Result<bool, MetadataError> {
        Ok(self.ensure_data()?.linking_to.is_some())
    }

    /// Get LinkingTo dependencies (packages needed for C/C++ headers at compile time)
    pub fn linking_to(&self) -> Result<Vec<RDependency>, MetadataError> {
        Ok(self
            .ensure_data()?
            .linking_to
            .as_ref()
            .map(|s| parse_r_dependencies(s))
            .unwrap_or_default())
    }

    /// Returns input globs for R package files
    pub fn input_globs(&self) -> BTreeSet<String> {
        let mut globs = BTreeSet::new();

        if self.description_data.get().is_some() {
            globs.insert("DESCRIPTION".to_string());
        }

        globs
    }

    /// Get R package dependencies from Imports field
    pub fn imports(&self) -> Result<Vec<RDependency>, MetadataError> {
        Ok(self
            .ensure_data()?
            .imports
            .as_ref()
            .map(|s| parse_r_dependencies(s))
            .unwrap_or_default())
    }

    /// Get R package dependencies from Depends field
    pub fn depends(&self) -> Result<Vec<RDependency>, MetadataError> {
        Ok(self
            .ensure_data()?
            .depends
            .as_ref()
            .map(|s| parse_r_dependencies(s))
            .unwrap_or_default())
    }

    /// Get all runtime dependencies (Imports + Depends, excluding R itself)
    pub fn runtime_dependencies(&self) -> Result<Vec<RDependency>, MetadataError> {
        let mut deps = Vec::new();

        // Add Imports
        deps.extend(self.imports()?);

        // Add Depends (but filter out R itself as it's handled separately)
        deps.extend(self.depends()?.into_iter().filter(|d| d.name != "R"));

        Ok(deps)
    }
}

impl MetadataProvider for DescriptionMetadataProvider {
    type Error = MetadataError;

    fn name(&mut self) -> Result<Option<String>, Self::Error> {
        Ok(self.ensure_data()?.package.clone())
    }

    fn version(&mut self) -> Result<Option<Version>, Self::Error> {
        let data = self.ensure_data()?;
        match &data.version {
            Some(v) => Ok(Some(Version::from_str(v)?)),
            None => Ok(None),
        }
    }

    fn description(&mut self) -> Result<Option<String>, Self::Error> {
        Ok(self.ensure_data()?.description.clone())
    }

    fn homepage(&mut self) -> Result<Option<String>, Self::Error> {
        Ok(self.ensure_data()?.url.clone())
    }

    fn license(&mut self) -> Result<Option<String>, Self::Error> {
        let data = self.ensure_data()?;
        Ok(data.license.as_ref().and_then(|l| {
            let parsed = parse_r_license(l);
            parsed.license
        }))
    }

    fn summary(&mut self) -> Result<Option<String>, Self::Error> {
        Ok(self.ensure_data()?.title.clone())
    }

    fn repository(&mut self) -> Result<Option<String>, Self::Error> {
        Ok(self.ensure_data()?.bug_reports.clone())
    }

    fn license_files(&mut self) -> Result<Option<Vec<String>>, Self::Error> {
        let data = self.ensure_data()?;
        Ok(data.license.as_ref().and_then(|l| {
            let parsed = parse_r_license(l);
            parsed.license_file.map(|f| vec![f])
        }))
    }

    fn documentation(&mut self) -> Result<Option<String>, Self::Error> {
        // R packages don't typically have separate docs URL
        Ok(None)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    fn create_test_description(content: &str) -> TempDir {
        let temp_dir = TempDir::new().unwrap();
        let desc_path = temp_dir.path().join("DESCRIPTION");
        fs_err::write(desc_path, content).unwrap();
        temp_dir
    }

    #[test]
    fn test_parse_basic_description() {
        let content = r#"Package: testpkg
Version: 1.0.0
Title: Test Package
Description: A test package for testing
License: GPL-3
"#;
        let temp_dir = create_test_description(content);
        let mut provider = DescriptionMetadataProvider::new(temp_dir.path());

        assert_eq!(provider.name().unwrap(), Some("testpkg".to_string()));
        assert_eq!(provider.version().unwrap().unwrap().to_string(), "1.0.0");
        assert_eq!(
            provider.license().unwrap(),
            Some("GPL-3.0-only".to_string())
        );
        assert_eq!(
            provider.summary().unwrap(),
            Some("Test Package".to_string())
        );
        assert_eq!(
            provider.description().unwrap(),
            Some("A test package for testing".to_string())
        );
    }

    #[test]
    fn test_multiline_description() {
        let content = r#"Package: testpkg
Version: 1.0.0
Description: This is a long description
    that spans multiple lines
    with continuation.
License: MIT
"#;
        let temp_dir = create_test_description(content);
        let mut provider = DescriptionMetadataProvider::new(temp_dir.path());

        let desc = provider.description().unwrap().unwrap();
        assert!(desc.contains("long description"));
        assert!(desc.contains("multiple lines"));
        assert!(desc.contains("continuation"));
    }

    #[test]
    fn test_all_fields() {
        let content = r#"Package: fullpkg
Version: 2.1.3
Title: Full Package Example
Description: A comprehensive example
    with all fields populated.
License: GPL-3
URL: https://github.com/user/fullpkg
BugReports: https://github.com/user/fullpkg/issues
Maintainer: John Doe <john@example.com>
LinkingTo: Rcpp, RcppArmadillo
Depends: R (>= 3.5.0), dplyr
Imports: ggplot2, tidyr
"#;
        let temp_dir = create_test_description(content);
        let mut provider = DescriptionMetadataProvider::new(temp_dir.path());

        assert_eq!(provider.name().unwrap(), Some("fullpkg".to_string()));
        assert_eq!(provider.version().unwrap().unwrap().to_string(), "2.1.3");
        assert_eq!(
            provider.summary().unwrap(),
            Some("Full Package Example".to_string())
        );
        assert_eq!(
            provider.license().unwrap(),
            Some("GPL-3.0-only".to_string())
        );
        assert_eq!(
            provider.homepage().unwrap(),
            Some("https://github.com/user/fullpkg".to_string())
        );
        assert_eq!(
            provider.repository().unwrap(),
            Some("https://github.com/user/fullpkg/issues".to_string())
        );
        assert!(provider.has_linking_to().unwrap());
    }

    #[test]
    fn test_license_mit_with_file() {
        let content = r#"Package: testpkg
Version: 1.0.0
License: MIT + file LICENSE
"#;
        let temp_dir = create_test_description(content);
        let mut provider = DescriptionMetadataProvider::new(temp_dir.path());

        assert_eq!(provider.license().unwrap(), Some("MIT".to_string()));
        assert_eq!(
            provider.license_files().unwrap(),
            Some(vec!["LICENSE".to_string()])
        );
    }

    #[test]
    fn test_imports_and_depends_extraction() {
        let content = r#"Package: webmockr
Version: 2.2.1.92
Depends:
    R(>= 4.1.0)
Imports:
    curl,
    jsonlite,
    magrittr (>= 1.5),
    R6 (>= 2.1.3)
"#;
        let temp_dir = create_test_description(content);
        let provider = DescriptionMetadataProvider::new(temp_dir.path());

        let imports = provider.imports().unwrap();
        assert_eq!(imports.len(), 4);
        assert_eq!(imports[0].name, "curl");
        assert_eq!(imports[2].name, "magrittr");
        assert_eq!(imports[2].version, Some(">= 1.5".to_string()));

        let depends = provider.depends().unwrap();
        assert_eq!(depends.len(), 1);
        assert_eq!(depends[0].name, "R");
        assert_eq!(depends[0].version, Some(">= 4.1.0".to_string()));

        // Test runtime_dependencies (should exclude R)
        let runtime = provider.runtime_dependencies().unwrap();
        assert_eq!(runtime.len(), 4); // Only Imports, not R from Depends
        assert!(runtime.iter().all(|d| d.name != "R"));
    }

    mod dependency_parser_tests {
        use super::*;

        #[test]
        fn test_parse_simple_dependencies() {
            let deps = parse_r_dependencies("curl, jsonlite, magrittr");
            assert_eq!(deps.len(), 3);
            assert_eq!(
                deps[0],
                RDependency {
                    name: "curl".to_string(),
                    version: None
                }
            );
        }

        #[test]
        fn test_parse_dependencies_with_versions() {
            let deps =
                parse_r_dependencies("magrittr (>= 1.5), R6 (>= 2.1.3), urltools (>= 1.6.0)");
            assert_eq!(deps.len(), 3);
            assert_eq!(
                deps[0],
                RDependency {
                    name: "magrittr".to_string(),
                    version: Some(">= 1.5".to_string())
                }
            );
        }

        #[test]
        fn test_r_package_to_conda() {
            assert_eq!(r_package_to_conda("curl"), "r-curl");
            assert_eq!(r_package_to_conda("jsonlite"), "r-jsonlite");
            assert_eq!(r_package_to_conda("R6"), "r-r6");
            assert_eq!(r_package_to_conda("R"), "r-base");
        }

        #[test]
        fn test_r_version_to_conda() {
            assert_eq!(r_version_to_conda(">= 1.5"), ">=1.5");
            assert_eq!(r_version_to_conda("(>= 2.1.3)"), ">=2.1.3");
        }

        #[test]
        fn test_is_builtin_package() {
            assert!(is_builtin_package("stats"));
            assert!(is_builtin_package("MASS"));
            assert!(is_builtin_package("mass")); // case insensitive
            assert!(!is_builtin_package("curl"));
            assert!(!is_builtin_package("ggplot2"));
        }
    }

    mod license_parser_tests {
        use super::*;

        #[test]
        fn test_mit_with_file() {
            let parsed = parse_r_license("MIT + file LICENSE");
            assert_eq!(
                parsed,
                ParsedLicense {
                    license: Some("MIT".to_string()),
                    license_file: Some("LICENSE".to_string()),
                }
            );
        }

        #[test]
        fn test_gpl_3_simple() {
            let parsed = parse_r_license("GPL-3");
            assert_eq!(
                parsed,
                ParsedLicense {
                    license: Some("GPL-3.0-only".to_string()),
                    license_file: None,
                }
            );
        }

        #[test]
        fn test_gpl_version_range() {
            let parsed = parse_r_license("GPL (>= 2)");
            assert_eq!(
                parsed,
                ParsedLicense {
                    license: Some("GPL-2.0-or-later".to_string()),
                    license_file: None,
                }
            );
        }

        #[test]
        fn test_file_only() {
            let parsed = parse_r_license("file LICENSE");
            assert_eq!(
                parsed,
                ParsedLicense {
                    license: None,
                    license_file: Some("LICENSE".to_string()),
                }
            );
        }

        #[test]
        fn test_bsd_3_clause_with_file() {
            let parsed = parse_r_license("BSD_3_clause + file LICENSE");
            assert_eq!(
                parsed,
                ParsedLicense {
                    license: Some("BSD-3-Clause".to_string()),
                    license_file: Some("LICENSE".to_string()),
                }
            );
        }
    }
}
