use crate::{
    config,
    config::tree::{keys, Gitoxide, Key, Section},
};

impl Gitoxide {
    /// The `gitoxide.allow` section.
    pub const ALLOW: Allow = Allow;
    /// The `gitoxide.author` section.
    pub const AUTHOR: Author = Author;
    /// The `gitoxide.core` section.
    pub const CORE: Core = Core;
    /// The `gitoxide.commit` section.
    pub const COMMIT: Commit = Commit;
    /// The `gitoxide.committer` section.
    pub const COMMITTER: Committer = Committer;
    /// The `gitoxide.credentials` section.
    pub const CREDENTIALS: Credentials = Credentials;
    /// The `gitoxide.http` section.
    pub const HTTP: Http = Http;
    /// The `gitoxide.https` section.
    pub const HTTPS: Https = Https;
    /// The `gitoxide.objects` section.
    pub const OBJECTS: Objects = Objects;
    /// The `gitoxide.ssh` section.
    pub const SSH: Ssh = Ssh;
    /// The `gitoxide.user` section.
    pub const USER: User = User;
    /// The `gitoxide.pathspec` section.
    pub const PATHSPEC: Pathspec = Pathspec;

    /// The `gitoxide.userAgent` Key.
    pub const USER_AGENT: keys::Any = keys::Any::new("userAgent", &config::Tree::GITOXIDE).with_note(
        "The user agent presented on the git protocol layer, serving as fallback for when no `http.userAgent` is set",
    );
    /// The `gitoxide.tracePacket` Key.
    pub const TRACE_PACKET: keys::Boolean = keys::Boolean::new_boolean("tracePacket", &config::Tree::GITOXIDE)
        .with_environment_override("GIT_TRACE_PACKET");
    /// The `gitoxide.parsePrecious` Key.
    pub const PARSE_PRECIOUS: keys::Boolean = keys::Boolean::new_boolean("parsePrecious", &config::Tree::GITOXIDE)
        .with_environment_override("GIX_PARSE_PRECIOUS");
}

impl Section for Gitoxide {
    fn name(&self) -> &str {
        "gitoxide"
    }

    fn keys(&self) -> &[&dyn Key] {
        &[&Self::USER_AGENT, &Self::TRACE_PACKET, &Self::PARSE_PRECIOUS]
    }

    fn sub_sections(&self) -> &[&dyn Section] {
        &[
            &Self::ALLOW,
            &Self::AUTHOR,
            &Self::CORE,
            &Self::COMMIT,
            &Self::COMMITTER,
            &Self::CREDENTIALS,
            &Self::HTTP,
            &Self::HTTPS,
            &Self::OBJECTS,
            &Self::SSH,
            &Self::USER,
            &Self::PATHSPEC,
        ]
    }
}

mod subsections {
    use crate::config::{
        tree::{http, keys, Gitoxide, Key, Section},
        Tree,
    };

    /// The `Core` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Core;

    /// The `gitoxide.allow.protocolFromUser` key.
    pub type RefsNamespace = keys::Any<super::validate::RefsNamespace>;

    impl RefsNamespace {
        /// Derive the negotiation algorithm identified by `name`, case-sensitively.
        pub fn try_into_refs_namespace(
            &'static self,
            name: std::borrow::Cow<'_, crate::bstr::BStr>,
        ) -> Result<gix_ref::Namespace, crate::config::refs_namespace::Error> {
            gix_ref::namespace::expand(name.as_ref())
                .map_err(|err| crate::config::key::Error::from_value(self, name.into_owned()).with_source(err))
        }
    }

    impl Core {
        /// The `gitoxide.core.defaultPackCacheMemoryLimit` key.
        pub const DEFAULT_PACK_CACHE_MEMORY_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("defaultPackCacheMemoryLimit", &Gitoxide::CORE).with_note(
                "If unset, we default to 96MB memory cap for the default 64 slot LRU cache for object deltas.",
            );
        /// The `gitoxide.core.useNsec` key.
        pub const USE_NSEC: keys::Boolean = keys::Boolean::new_boolean("useNsec", &Gitoxide::CORE)
            .with_note("A runtime version of the USE_NSEC build flag.");

        /// The `gitoxide.core.useStdev` key.
        pub const USE_STDEV: keys::Boolean = keys::Boolean::new_boolean("useStdev", &Gitoxide::CORE)
            .with_note("A runtime version of the USE_STDEV build flag.");

        /// The `gitoxide.core.protectWindows` key.
        pub const PROTECT_WINDOWS: keys::Boolean = keys::Boolean::new_boolean("protectWindows", &Gitoxide::CORE)
            .with_note("enable protections that are enabled by default on Windows");

        /// The `gitoxide.core.shallowFile` key.
        pub const SHALLOW_FILE: keys::Path = keys::Path::new_path("shallowFile", &Gitoxide::CORE)
            .with_environment_override("GIT_SHALLOW_FILE")
            .with_deviation(
                "relative file paths will always be made relative to the git-common-dir, whereas `git` keeps them as is.",
            );

        /// The `gitoxide.core.filterProcessDelay` key (default `true`).
        ///
        /// It controls whether or not long running filter driver processes can use the 'delay' capability.
        pub const FILTER_PROCESS_DELAY: keys::Boolean =
            keys::Boolean::new_boolean("filterProcessDelay", &Gitoxide::CORE);

        /// The `gitoxide.core.externalCommandStderr` key (default `true`).
        ///
        /// If `true`, the default, `stderr` of worktree filter programs, or any other git-context bearing command
        /// invoked will be inherited.
        /// If `false`, it will be suppressed completely.
        pub const EXTERNAL_COMMAND_STDERR: keys::Boolean =
            keys::Boolean::new_boolean("externalCommandStderr", &Gitoxide::CORE)
                .with_environment_override("GIX_EXTERNAL_COMMAND_STDERR");

        /// The `gitoxide.core.refsNamespace` key.
        pub const REFS_NAMESPACE: RefsNamespace =
            keys::Any::new_with_validate("refsNamespace", &Gitoxide::CORE, super::validate::RefsNamespace)
                .with_environment_override("GIT_NAMESPACE");
    }

    impl Section for Core {
        fn name(&self) -> &str {
            "core"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::DEFAULT_PACK_CACHE_MEMORY_LIMIT,
                &Self::USE_NSEC,
                &Self::USE_STDEV,
                &Self::SHALLOW_FILE,
                &Self::PROTECT_WINDOWS,
                &Self::FILTER_PROCESS_DELAY,
                &Self::EXTERNAL_COMMAND_STDERR,
                &Self::REFS_NAMESPACE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `Http` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Http;

    impl Http {
        /// The `gitoxide.http.proxy` key.
        pub const PROXY: keys::String =
            keys::String::new_string("proxy", &Gitoxide::HTTP).with_environment_override("http_proxy");
        /// The `gitoxide.http.allProxy` key.
        pub const ALL_PROXY: keys::String = keys::String::new_string("allProxy", &Gitoxide::HTTP)
            .with_environment_override("all_proxy")
            .with_note("fallback environment is `ALL_PROXY`");
        /// The `gitoxide.http.verbose` key.
        ///
        /// If set, curl will be configured to log verbosely.
        pub const VERBOSE: keys::Boolean = keys::Boolean::new_boolean("verbose", &Gitoxide::HTTP)
            .with_environment_override("GIT_CURL_VERBOSE")
            .with_deviation("we parse it as boolean for convenience (infallible) but git only checks the presence");
        /// The `gitoxide.http.noProxy` key.
        pub const NO_PROXY: keys::String = keys::String::new_string("noProxy", &Gitoxide::HTTP)
            .with_environment_override("no_proxy")
            .with_note("fallback environment is `NO_PROXY`");
        /// The `gitoxide.http.connectTimeout` key.
        pub const CONNECT_TIMEOUT: keys::DurationInMilliseconds =
            keys::DurationInMilliseconds::new_duration("connectTimeout", &Gitoxide::HTTP).with_note(
                "entirely new, and in milliseconds, to describe how long to wait until a connection attempt is aborted",
            );
        /// The `gitoxide.http.sslVersionMin` key.
        pub const SSL_VERSION_MIN: http::SslVersion =
            http::SslVersion::new_ssl_version("sslVersionMin", &Gitoxide::HTTP).with_note(
                "entirely new to set the lower bound for the allowed ssl version range. Overwrites the min bound of `http.sslVersion` if set. Min and Max must be set to become effective.",
            );
        /// The `gitoxide.http.sslVersionMax` key.
        pub const SSL_VERSION_MAX: http::SslVersion =
            http::SslVersion::new_ssl_version("sslVersionMax", &Gitoxide::HTTP).with_note(
                "entirely new to set the upper bound for the allowed ssl version range. Overwrites the max bound of `http.sslVersion` if set. Min and Max must be set to become effective.",
            );
        /// The `gitoxide.http.sslNoVerify` key.
        ///
        /// If set, disable SSL verification. Using this is discouraged as it can lead to
        /// various security risks. An example where this may be needed is when an internal
        /// git server uses a self-signed certificate and the user accepts the associated security risks.
        pub const SSL_NO_VERIFY: keys::Boolean = keys::Boolean::new_boolean("sslNoVerify", &Gitoxide::HTTP)
            .with_environment_override("GIT_SSL_NO_VERIFY")
            .with_note("used to disable SSL verification. When this is enabled it takes priority over http.sslVerify");
        /// The `gitoxide.http.proxyAuthMethod` key.
        pub const PROXY_AUTH_METHOD: http::ProxyAuthMethod =
            http::ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &Gitoxide::HTTP)
                .with_environment_override("GIT_HTTP_PROXY_AUTHMETHOD");
    }

    impl Section for Http {
        fn name(&self) -> &str {
            "http"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::PROXY,
                &Self::ALL_PROXY,
                &Self::VERBOSE,
                &Self::NO_PROXY,
                &Self::CONNECT_TIMEOUT,
                &Self::SSL_VERSION_MIN,
                &Self::SSL_VERSION_MAX,
                &Self::SSL_NO_VERIFY,
                &Self::PROXY_AUTH_METHOD,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `Https` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Https;

    impl Https {
        /// The `gitoxide.https.proxy` key.
        pub const PROXY: keys::String = keys::String::new_string("proxy", &Gitoxide::HTTPS)
            .with_environment_override("HTTPS_PROXY")
            .with_note("fallback environment variable is `https_proxy`");
    }

    impl Section for Https {
        fn name(&self) -> &str {
            "https"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::PROXY]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `allow` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Allow;

    /// The `gitoxide.allow.protocolFromUser` key.
    pub type ProtocolFromUser = keys::Any<super::validate::ProtocolFromUser>;

    impl Allow {
        /// The `gitoxide.allow.protocolFromUser` key.
        pub const PROTOCOL_FROM_USER: ProtocolFromUser = ProtocolFromUser::new_with_validate(
            "protocolFromUser",
            &Gitoxide::ALLOW,
            super::validate::ProtocolFromUser,
        )
        .with_environment_override("GIT_PROTOCOL_FROM_USER");
    }

    impl Section for Allow {
        fn name(&self) -> &str {
            "allow"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::PROTOCOL_FROM_USER]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `author` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Author;

    impl Author {
        /// The `gitoxide.author.nameFallback` key.
        pub const NAME_FALLBACK: keys::Any =
            keys::Any::new("nameFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_NAME");
        /// The `gitoxide.author.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_EMAIL");
    }

    impl Section for Author {
        fn name(&self) -> &str {
            "author"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `user` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct User;

    impl User {
        /// The `gitoxide.user.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::USER).with_environment_override("EMAIL");
    }

    impl Section for User {
        fn name(&self) -> &str {
            "user"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `ssh` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Ssh;

    impl Ssh {
        /// The `gitoxide.ssh.commandWithoutShellFallback` key.
        pub const COMMAND_WITHOUT_SHELL_FALLBACK: keys::Executable =
            keys::Executable::new_executable("commandWithoutShellFallback", &Gitoxide::SSH)
                .with_environment_override("GIT_SSH")
                .with_note("is always executed without shell and treated as fallback");
    }

    impl Section for Ssh {
        fn name(&self) -> &str {
            "ssh"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::COMMAND_WITHOUT_SHELL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `pathspec` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Pathspec;

    impl Pathspec {
        /// The `gitoxide.pathspec.glob` key.
        pub const GLOB: keys::Boolean = keys::Boolean::new_boolean("glob", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_GLOB_PATHSPECS")
            .with_note("pathspec wildcards don't match the slash character, then needing '**' to get past them");
        /// The `gitoxide.pathspec.noglob` key.
        pub const NOGLOB: keys::Boolean = keys::Boolean::new_boolean("noglob", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_NOGLOB_PATHSPECS")
            .with_note("Enable literal matching for glob patterns, effectively disabling globbing");
        /// The `gitoxide.pathspec.literal` key.
        pub const LITERAL: keys::Boolean = keys::Boolean::new_boolean("literal", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_LITERAL_PATHSPECS")
            .with_note("Make the entire spec used verbatim, the only way to get ':()name' verbatim for instance");
        /// The `gitoxide.pathspec.icase` key.
        pub const ICASE: keys::Boolean = keys::Boolean::new_boolean("icase", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_ICASE_PATHSPECS")
            .with_note("Compare string in a case-insensitive manner");
        /// The `gitoxide.pathspec.inheritIgnoreCase` key, defaulting to `true` if unspecified.
        /// If set, pathspecs will automatically be match case-insensitively if the underlying filesystem is configured that way.
        pub const INHERIT_IGNORE_CASE: keys::Boolean =
            keys::Boolean::new_boolean("inheritIgnoreCase", &Gitoxide::PATHSPEC)
                .with_note("Inherit `core.ignoreCase` for defaults in pathspecs");
        /// The default value for `gitoxide.pathspec.inheritIgnoreCase`.
        pub const INHERIT_IGNORE_CASE_DEFAULT: bool = true;
    }

    impl Section for Pathspec {
        fn name(&self) -> &str {
            "pathspec"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::GLOB,
                &Self::NOGLOB,
                &Self::LITERAL,
                &Self::ICASE,
                &Self::INHERIT_IGNORE_CASE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `objects` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Objects;

    impl Objects {
        /// The `gitoxide.objects.cacheLimit` key.
        pub const CACHE_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("cacheLimit", &Gitoxide::OBJECTS)
                .with_note("If unset or 0, there is no object cache")
                .with_environment_override("GIX_OBJECT_CACHE_MEMORY");
        /// The `gitoxide.objects.allocLimit` key.
        ///
        /// Implemented for:
        /// - packed object decoding in `gix-pack::data::File`
        /// - multi-pack-index name decoding in `gix-pack::multi_index::File`
        /// - direct packed-object inflation in `gix-odb`
        pub const ALLOC_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("allocLimit", &Gitoxide::OBJECTS)
                .with_environment_override("GIT_ALLOC_LIMIT")
                .with_note("The maximum size of a single allocation caused by user-controlled on-disk object data. In fact, it's used for all untrusted data that causes allocations.");
        /// The default `allocLimitIfReducedTrust` in bytes applied when a repository is opened with reduced trust
        /// and no explicit [`ALLOC_LIMIT`][Self::ALLOC_LIMIT] exists.
        pub const ALLOC_LIMIT_IF_REDUCED_TRUST_DEFAULT: usize = 16 * 1024 * 1024;
        /// The `gitoxide.objects.allocLimitIfReducedTrust` key.
        ///
        /// If set, repositories opened with reduced trust receive [`ALLOC_LIMIT`][Self::ALLOC_LIMIT] set to this value
        /// when no explicit allocation limit exists. A value of `0` disables this fallback.
        pub const ALLOC_LIMIT_IF_REDUCED_TRUST: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("allocLimitIfReducedTrust", &Gitoxide::OBJECTS).with_note(
                "The default allocation limit used for reduced-trust repositories when no explicit allocLimit is configured; set to 0 to disable it",
            );
        /// The `gitoxide.objects.noReplace` key.
        pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS);
        /// The `gitoxide.objects.replaceRefBase` key.
        pub const REPLACE_REF_BASE: keys::Any =
            keys::Any::new("replaceRefBase", &Gitoxide::OBJECTS).with_environment_override("GIT_REPLACE_REF_BASE");
    }

    impl Section for Objects {
        fn name(&self) -> &str {
            "objects"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::CACHE_LIMIT,
                &Self::ALLOC_LIMIT,
                &Self::ALLOC_LIMIT_IF_REDUCED_TRUST,
                &Self::REPLACE_REF_BASE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `committer` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Committer;

    impl Committer {
        /// The `gitoxide.committer.nameFallback` key.
        pub const NAME_FALLBACK: keys::Any =
            keys::Any::new("nameFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_NAME");
        /// The `gitoxide.committer.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_EMAIL");
    }

    impl Section for Committer {
        fn name(&self) -> &str {
            "committer"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `credentials` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Credentials;
    impl Credentials {
        /// The `gitoxide.credentials.terminalPrompt` key.
        pub const TERMINAL_PROMPT: keys::Boolean = keys::Boolean::new_boolean("terminalPrompt", &Gitoxide::CREDENTIALS)
            .with_note("This is a custom addition to provide an alternative to the respective environment variable.")
            .with_environment_override("GIT_TERMINAL_PROMPT");

        /// The `gitoxide.credentials.helperStderr` key to control what happens with the credential helpers `stderr`.
        ///
        /// If `true`, the default, `stderr` of credential helper programs will be inherited, just like with `git`.
        /// If `false`, will be suppressed completely.
        pub const HELPER_STDERR: keys::Boolean = keys::Boolean::new_boolean("helperStderr", &Gitoxide::CREDENTIALS)
            .with_environment_override("GIX_CREDENTIALS_HELPER_STDERR");
    }

    impl Section for Credentials {
        fn name(&self) -> &str {
            "credentials"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::TERMINAL_PROMPT, &Self::HELPER_STDERR]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `commit` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Commit;

    impl Commit {
        /// The `gitoxide.commit.authorDate` key.
        pub const AUTHOR_DATE: keys::Any =
            keys::Any::new("authorDate", &Gitoxide::COMMIT).with_environment_override("GIT_AUTHOR_DATE");
        /// The `gitoxide.commit.committerDate` key.
        pub const COMMITTER_DATE: keys::Any =
            keys::Any::new("committerDate", &Gitoxide::COMMIT).with_environment_override("GIT_COMMITTER_DATE");
    }

    impl Section for Commit {
        fn name(&self) -> &str {
            "commit"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }
}
pub use subsections::{Allow, Author, Commit, Committer, Core, Credentials, Http, Https, Objects, Pathspec, Ssh, User};

pub mod validate {
    use std::error::Error;

    use crate::{bstr::BStr, config::tree::keys::Validate};

    #[derive(Clone, Copy)]
    pub struct ProtocolFromUser;
    impl Validate for ProtocolFromUser {
        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
            if value != "1" {
                return Err("GIT_PROTOCOL_FROM_USER is either unset or as the value '1'".into());
            }
            Ok(())
        }
    }

    #[derive(Clone, Copy)]
    pub struct RefsNamespace;
    impl Validate for RefsNamespace {
        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
            super::Core::REFS_NAMESPACE.try_into_refs_namespace(value.into())?;
            Ok(())
        }
    }
}
