use indexmap::IndexMap;
use serde::de::IgnoredAny;
use serde::Deserialize;
use toml;

use crate::debian::control::PkgTestRestriction;
use crate::errors::Result;

use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

pub const RUST_MAINT: &str =
    "Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>";

pub const DEFAULT_REPACK_SUFFIX: &str = "ds";

#[derive(Deserialize, Debug, Clone)]
#[serde(default)]
pub struct Config {
    pub bin: Option<bool>,
    pub bin_name: String,
    pub lib: Option<bool>,
    pub semver_suffix: bool,
    pub overlay: Option<PathBuf>,
    pub excludes: Option<Vec<String>>,
    pub whitelist: Option<Vec<String>>,
    pub repack_suffix: Option<String>,
    pub allow_prerelease_deps: bool,
    pub crate_src_path: Option<PathBuf>,
    pub summary: Option<String>,
    pub description: Option<String>,
    pub maintainer: String,
    pub uploaders: Option<Vec<String>>,
    pub collapse_features: bool,
    pub remove_features: Option<Vec<String>>,
    pub remove_target_types: Option<Vec<TargetType>>,
    pub filter_targets: Option<Vec<FilterTarget>>,
    pub requires_root: Option<String>,

    pub source: Option<SourceOverride>,
    pub packages: IndexMap<String, PackageOverride>,

    #[serde(flatten)]
    pub unknown_fields: HashMap<String, IgnoredAny>,
}

#[derive(Deserialize, Debug, Clone, Default)]
pub struct SourceOverride {
    section: Option<String>,
    policy: Option<String>,
    homepage: Option<String>,
    vcs_git: Option<String>,
    vcs_browser: Option<String>,
    build_depends: Option<Vec<String>>,
    build_depends_arch: Option<Vec<String>>,
    build_depends_indep: Option<Vec<String>>,
    build_depends_excludes: Option<Vec<String>>,
    build_features: Option<Vec<String>>,
    skip_nocheck: Option<bool>,

    #[serde(flatten)]
    pub unknown_fields: HashMap<String, IgnoredAny>,
}

impl SourceOverride {
    #[expect(clippy::too_many_arguments)]
    pub fn new(
        section: Option<String>,
        policy: Option<String>,
        homepage: Option<String>,
        vcs_git: Option<String>,
        vcs_browser: Option<String>,
        build_depends: Option<Vec<String>>,
        build_depends_arch: Option<Vec<String>>,
        build_depends_indep: Option<Vec<String>>,
        build_depends_excludes: Option<Vec<String>>,
        build_features: Option<Vec<String>>,
        skip_nocheck: Option<bool>,
    ) -> Self {
        Self {
            section,
            policy,
            homepage,
            vcs_git,
            vcs_browser,
            build_depends,
            build_depends_arch,
            build_depends_indep,
            build_depends_excludes,
            build_features,
            skip_nocheck,
            unknown_fields: HashMap::new(),
        }
    }
}
#[derive(Deserialize, Debug, Clone, Default)]
pub struct PackageOverride {
    section: Option<String>,
    summary: Option<String>,
    description: Option<String>,
    architecture: Option<Vec<String>>,
    multi_arch: Option<String>,
    depends: Option<Vec<String>>,
    recommends: Option<Vec<String>>,
    suggests: Option<Vec<String>>,
    provides: Option<Vec<String>>,
    breaks: Option<Vec<String>>,
    replaces: Option<Vec<String>>,
    conflicts: Option<Vec<String>>,
    extra_lines: Option<Vec<String>>,
    test_is_broken: Option<bool>,
    test_architecture: Option<Vec<String>>,
    test_depends: Option<Vec<String>>,
    test_restrictions: Option<Vec<PkgTestRestriction>>,
    test_command: Option<String>,

    #[serde(flatten)]
    pub unknown_fields: HashMap<String, IgnoredAny>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
/// Whether to automatically remove dependencies for certain targets
pub enum FilterTarget {
    /// Targets that do match any Debian release or ports architecture
    Debian,
    /// Targets that do not match WASM targets
    Wasm,
}
impl FilterTarget {
    pub(crate) fn target_list(&self) -> &[&str] {
        match self {
            Self::Debian => &[
                // Release architectures
                "aarch64-unknown-linux-gnu",
                "armv7-unknown-linux-gnueabihf",
                "i686-unknown-linux-gnu",
                "loongarch64-unknown-linux-gnu",
                "powerpc64le-unknown-linux-gnu",
                "riscv64gc-unknown-linux-gnu",
                "s390x-unknown-linux-gnu",
                "x86_64-unknown-linux-gnu",
                // Port architectures
                "i686-unknown-hurd-gnu",
                "m68k-unknown-linux-gnu", // soon ;)
                "powerpc-unknown-linux-gnu",
                "powerpc64-unknown-linux-gnu",
                "sparc64-unknown-linux-gnu",
                "x86_64-unknown-hurd-gnu",
                "x86_64-unknown-linux-gnux32",
            ],
            Self::Wasm => &[
                "wasm32-unknown-unknown",
                "wasm32-wasip1",
                "wasm32-wasip1-threads",
                "wasm32-wasip2",
                "wasm32-wasip3",
            ],
        }
    }
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
/// Simplified version of [`cargo::core::manifest::TargetKind`]
pub enum TargetType {
    /// Benchmark
    Bench,
    /// Example
    Example,
    /// Integration test
    Test,
}

impl std::fmt::Display for TargetType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TargetType::Bench => f.write_str("bench"),
            TargetType::Example => f.write_str("example"),
            TargetType::Test => f.write_str("test"),
        }
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            bin: None,
            bin_name: "<default>".to_string(),
            lib: None,
            semver_suffix: false,
            overlay: None,
            excludes: None,
            repack_suffix: None,
            whitelist: None,
            allow_prerelease_deps: false,
            crate_src_path: None,
            summary: None,
            description: None,
            maintainer: RUST_MAINT.to_string(),
            uploaders: None,
            collapse_features: true,
            remove_features: None,
            remove_target_types: None,
            filter_targets: None,
            source: None,
            packages: IndexMap::new(),
            requires_root: None,
            unknown_fields: HashMap::new(),
        }
    }
}

impl Config {
    pub fn parse(src: &Path) -> Result<Config> {
        let mut config_file = File::open(src)?;
        let mut content = String::new();
        config_file.read_to_string(&mut content)?;

        let config: Config = toml::from_str(&content)?;

        let mut unknown_fields = Vec::new();

        for field in config.unknown_fields.keys() {
            unknown_fields.push(field.clone());
        }

        if let Some(ref source) = config.source {
            for field in source.unknown_fields.keys() {
                unknown_fields.push(format!("source.{field}"));
            }
        }

        for field in config.packages.keys() {
            if PackageKey::from_key(field).is_none() {
                unknown_fields.push(format!("packages.{field}"));
            }
        }

        for (name, package) in &config.packages {
            for field in package.unknown_fields.keys() {
                unknown_fields.push(format!("packages.{name}.{field}"));
            }
        }

        if !unknown_fields.is_empty() {
            debcargo_warn!(
                "Warning: Unknown fields in {}: {:?}",
                src.display(),
                unknown_fields
            );
            debcargo_warn!("         These fields will be ignored. Please check for typos.");
        }

        Ok(config)
    }

    pub fn build_bin_package(&self) -> bool {
        self.bin.unwrap_or(!self.semver_suffix)
    }

    pub fn build_lib_package(&self) -> bool {
        self.lib.unwrap_or(true)
    }

    pub fn build_extra_package(&self) -> bool {
        self.configured_packages()
            .any(|p| matches!(p, PackageKey::Extra(_)))
    }

    pub fn overlay_dir(&self, config_path: Option<&Path>) -> Option<PathBuf> {
        Some(config_path?.parent()?.join(self.overlay.as_ref()?))
    }

    pub fn crate_src_path(&self, config_path: Option<&Path>) -> Option<PathBuf> {
        Some(config_path?.parent()?.join(self.crate_src_path.as_ref()?))
    }

    pub fn orig_tar_excludes(&self) -> Option<&Vec<String>> {
        self.excludes.as_ref()
    }

    pub fn orig_tar_whitelist(&self) -> Option<&Vec<String>> {
        self.whitelist.as_ref()
    }

    pub fn repack_suffix(&self) -> Option<&str> {
        self.repack_suffix
            .as_deref()
            .or_else(|| self.orig_tar_excludes().and(Some(DEFAULT_REPACK_SUFFIX)))
    }

    pub fn maintainer(&self) -> &str {
        self.maintainer.as_str()
    }

    pub fn uploaders(&self) -> Option<&Vec<String>> {
        self.uploaders.as_ref()
    }

    pub fn requires_root(&self) -> Option<&String> {
        self.requires_root.as_ref()
    }

    // Source shortcuts

    pub fn section(&self) -> Option<&str> {
        Some(self.source.as_ref()?.section.as_ref()?)
    }

    pub fn policy_version(&self) -> Option<&str> {
        Some(self.source.as_ref()?.policy.as_ref()?)
    }

    pub fn homepage(&self) -> Option<&str> {
        Some(self.source.as_ref()?.homepage.as_ref()?)
    }

    pub fn vcs_git(&self) -> Option<&str> {
        Some(self.source.as_ref()?.vcs_git.as_ref()?)
    }

    pub fn vcs_browser(&self) -> Option<&str> {
        Some(self.source.as_ref()?.vcs_browser.as_ref()?)
    }

    pub fn build_depends(&self) -> Option<&Vec<String>> {
        self.source.as_ref()?.build_depends.as_ref()
    }

    pub fn build_depends_arch(&self) -> Option<&Vec<String>> {
        self.source.as_ref()?.build_depends_arch.as_ref()
    }

    pub fn build_depends_indep(&self) -> Option<&Vec<String>> {
        self.source.as_ref()?.build_depends_indep.as_ref()
    }

    pub fn build_depends_excludes(&self) -> Option<&Vec<String>> {
        self.source.as_ref()?.build_depends_excludes.as_ref()
    }

    pub fn build_features(&self) -> Option<&Vec<String>> {
        self.source.as_ref()?.build_features.as_ref()
    }

    pub fn skip_nocheck(&self) -> Option<bool> {
        self.source.as_ref()?.skip_nocheck
    }

    // Packages accessors

    pub fn configured_packages(&'_ self) -> impl Iterator<Item = PackageKey<'_>> {
        self.packages.keys().filter_map(|k| PackageKey::from_key(k))
    }

    fn with_package<'a, T, F: FnOnce(&'a PackageOverride) -> Option<T>>(
        &'a self,
        key: PackageKey,
        f: F,
    ) -> Option<T> {
        self.packages.get(&key.key_string()[..]).and_then(f)
    }

    pub fn package_section(&self, key: PackageKey) -> Option<&str> {
        self.with_package(key, |pkg| pkg.section.as_deref())
    }

    pub fn package_summary(&self, key: PackageKey) -> Option<&str> {
        self.with_package(key, |pkg| pkg.summary.as_deref())
    }

    pub fn package_description(&self, key: PackageKey) -> Option<&str> {
        self.with_package(key, |pkg| pkg.description.as_deref())
    }

    pub fn package_architecture(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.architecture.as_ref())
    }

    pub fn package_multi_arch(&self, key: PackageKey) -> Option<&str> {
        self.with_package(key, |pkg| pkg.multi_arch.as_deref())
    }

    pub fn package_depends(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.depends.as_ref())
    }

    pub fn package_recommends(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.recommends.as_ref())
    }

    pub fn package_suggests(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.suggests.as_ref())
    }

    pub fn package_provides(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.provides.as_ref())
    }

    pub fn package_breaks(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.breaks.as_ref())
    }

    pub fn package_replaces(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.replaces.as_ref())
    }

    pub fn package_conflicts(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.conflicts.as_ref())
    }

    pub fn package_extra_lines(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.extra_lines.as_ref())
    }

    pub fn package_test_is_broken(&self, key: PackageKey) -> Option<bool> {
        self.with_package(key, |pkg| pkg.test_is_broken)
    }

    pub fn package_test_restrictions(&self, key: PackageKey) -> Option<&Vec<PkgTestRestriction>> {
        self.with_package(key, |pkg| pkg.test_restrictions.as_ref())
    }

    pub fn package_test_architecture(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.test_architecture.as_ref())
    }

    pub fn package_test_depends(&self, key: PackageKey) -> Option<&Vec<String>> {
        self.with_package(key, |pkg| pkg.test_depends.as_ref())
    }

    pub fn package_test_command(&self, key: PackageKey) -> Option<&String> {
        self.with_package(key, |pkg| pkg.test_command.as_ref())
    }
}

pub fn package_field_for_feature<'a, 'b, F: Fn(PackageKey) -> Option<&'a Vec<String>>>(
    get_field: F,
    feature: PackageKey<'b>,
    f_provides: &'b [&'b str],
) -> impl Iterator<Item = String> + use<'a, 'b, F> {
    Some(feature)
        .into_iter()
        .chain(f_provides.iter().map(|s| PackageKey::feature(s)))
        .flat_map(move |f| get_field(f).into_iter().flatten())
        .map(ToString::to_string)
}

#[derive(Clone, Copy)]
pub enum PackageKey<'a> {
    Bin,
    BareLib,
    FeatureLib(&'a str),
    Extra(&'a str),
}

impl<'a> PackageKey<'a> {
    pub fn feature(f: &'a str) -> PackageKey<'a> {
        use self::PackageKey::{BareLib, FeatureLib};
        if f.is_empty() {
            BareLib
        } else {
            FeatureLib(f)
        }
    }

    pub fn from_key(k: &'a str) -> Option<PackageKey<'a>> {
        use self::PackageKey::{BareLib, Bin, Extra, FeatureLib};
        Some(match k {
            "bin" => Bin,
            "lib" => BareLib,
            _ => {
                if let Some(feature) = k.strip_prefix("lib+") {
                    FeatureLib(feature)
                } else {
                    let package = k.strip_prefix("extra+")?;
                    Extra(package)
                }
            }
        })
    }

    fn key_string(&self) -> Cow<'static, str> {
        use self::PackageKey::{BareLib, Bin, Extra, FeatureLib};
        match self {
            Bin => "bin".into(),
            BareLib => "lib".into(),
            FeatureLib(feature) => format!("lib+{feature}").into(),
            Extra(package) => format!("extra+{package}").into(),
        }
    }
}

pub fn testing_ignore_debpolv() -> bool {
    std::env::var_os("DEBCARGO_TESTING_IGNORE_DEBIAN_POLICY_VIOLATION").as_deref()
        == Some(OsStr::new("1"))
}

pub fn testing_ruzt() -> bool {
    std::env::var_os("DEBCARGO_TESTING_RUZT").as_deref() == Some(OsStr::new("1"))
}
