"""
Distribution setups && support

Dist-like variable naming convention
====================================
* ``diststr``: Instance of ``str``: "buster-test-unstable"
* ``dist``: Instance of ``mini_buildd.dist.Dist``: diststr parsed && support
* ``distribution``: Instance of ``mini_buildd.model.distribution.Distribution``: Configured distribution
"""

import enum
import re
import logging
import functools
import abc

import distro_info

import debian.debian_support

from mini_buildd import util, call, sbuild

LOG = logging.getLogger(__name__)


class Archs:
    """Deliver lists of Debian architectures the current system can handle"""

    _NATIVE_ARCHS_MAP = {"amd64": ["i386"]}

    @classmethod
    def _atwarn(cls, e):
        LOG.warning(f"Consider to install ``arch-test`` to avoid: {e}")

    @classmethod
    def native(cls):
        try:
            return call.Call(["arch-test", "-n"]).check().stdout.split()
        except Exception as e:
            cls._atwarn(e)
            native = call.Call(["dpkg", "--print-architecture"]).check().stdout.strip()
            return [native] + cls._NATIVE_ARCHS_MAP.get(native, [])

    @classmethod
    def available(cls):
        try:
            return call.Call(["arch-test"]).check().stdout.split()
        except Exception as e:
            cls._atwarn(e)
            return cls.native()


class DistroInfo(distro_info.DistroInfo):
    def mbd_origin(self):
        return self._distro

    def mbd_release(self, codename):
        for r in self._releases:
            if r.series == codename:
                return r
        return None

    @abc.abstractmethod
    def mbd_lts(self):
        pass


class DebianDistroInfo(DistroInfo, distro_info.DebianDistroInfo):
    def mbd_lts(self):
        """
        .. tip:: What does ``LTS`` (mini-buildd speak) include for Debian?

           For mini-buildd, ``LTS`` means ``Debian Long Term Support (LTS)`` including ``Debian Extended Long Term Support (ELTS)``.

           See: https://www.debian.org/lts/, https://wiki.debian.org/LTS/Extended
        """
        return self.lts_supported() + self.elts_supported()


class UbuntuDistroInfo(DistroInfo, distro_info.UbuntuDistroInfo):
    def mbd_lts(self):
        """
        .. tip:: What does ``LTS`` (mini-buildd speak) include for Ubuntu?

           For mini-buildd, ``LTS`` means ``Ubuntu Long Term Support (LTS)`` including ``Ubuntu Extended Security Maintenance (ESM)``.

           See: https://ubuntu.com/about/release-cycle
        """
        return self.supported_esm()  # this includes LTS


#: Available distro infos
DISTRO_INFO = {
    "Debian": DebianDistroInfo(),
    "Ubuntu": UbuntuDistroInfo(),
}

#: Convenience component sorting to get some well known components sorted as we are used to
COMPONENTS_SORT_KEYS = {
    # Debian
    "main": "0",
    "contrib": "1",
    "non-free": "2",
    "non-free-firmware": "3",
    # Ubuntu (also uses 'main')
    "universe": "4",
    "restricted": "5",
    "multiverse": "6",
}


def sorted_components(components):
    return sorted(components, key=lambda c: COMPONENTS_SORT_KEYS.get(c, c))


APT_KEYS = {
    "Debian": {
        "bookworm": {
            "archive": "B7C5D7D6350947F8",    # Debian Archive Automatic Signing Key (12/bookworm) <ftpmaster@debian.org>
            "release": "F8D2585B8783D481",    # Debian Stable Release Key (12/bookworm) <debian-release@lists.debian.org>
            "security": "254CF3B5AEC0A8F0",   # Debian Security Archive Automatic Signing Key (12/bookworm) <ftpmaster@debian.org>
        },
        "bullseye": {
            "archive": "73A4F27B8DD47936",    # Debian Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>
            "release": "605C66F00D6C9793",    # Debian Stable Release Key (11/bullseye) <debian-release@lists.debian.org>
            "security": "A48449044AAD5C5D",   # Debian Security Archive Automatic Signing Key (11/bullseye) <ftpmaster@debian.org>
        },
        "buster": {
            "archive": "DC30D7C23CBBABEE",    # Debian Archive Automatic Signing Key (10/buster) <ftpmaster@debian.org>
            "release": "DCC9EFBF77E11517",    # Debian Stable Release Key (10/buster) <debian-release@lists.debian.org>
            "security": "4DFAB270CAA96DFA",   # Debian Security Archive Automatic Signing Key (10/buster) <ftpmaster@debian.org>
        },
        "stretch": {
            "archive": "E0B11894F66AEC98",    # Debian Archive Automatic Signing Key (9/stretch) <ftpmaster@debian.org>  (subkey 04EE7237B7D453EC)
            "release": "EF0F382A1A7B6500",    # Debian Stable Release Key (9/stretch) <debian-release@lists.debian.org>
            "security": "EDA0D2388AE22BA9",   # Debian Security Archive Automatic Signing Key (9/stretch) <ftpmaster@debian.org>
        },
        "jessie": {
            "archive": "7638D0442B90D010",    # Debian Archive Automatic Signing Key (8/jessie) <ftpmaster@debian.org>
            "release": "CBF8D6FD518E17E1",    # Jessie Stable Release Key <debian-release@lists.debian.org>
            "security": "9D6D8F6BC857C906",   # Debian Security Archive Automatic Signing Key (8/jessie) <ftpmaster@debian.org>
        },
        "wheezy": {
            "archive": "8B48AD6246925553",    # Debian Archive Automatic Signing Key (7.0/wheezy) <ftpmaster@debian.org>
            "release": "6FB2A1C265FFB764",    # Wheezy Stable Release Key <debian-release@lists.debian.org>
        },
        "squeeze": {
            "archive": "AED4B06F473041FA",    # Debian Archive Automatic Signing Key (6.0/squeeze) <ftpmaster@debian.org>
            "release": "64481591B98321F9",    # Squeeze Stable Release Key <debian-release@lists.debian.org>
        },
        "lenny": {
            "archive": "9AA38DCD55BE302B",    # Debian Archive Automatic Signing Key (5.0/lenny) <ftpmaster@debian.org>
            "release": "4D270D06F42584E6",    # Lenny Stable Release Key <debian-release@lists.debian.org>
        },
    },
    "Ubuntu": {
        "2018": "871920D1991BC93C",   # Ubuntu Archive Automatic Signing Key (2018) <ftpmaster@ubuntu.com>
        "2012": "3B4FE6ACC0B21F32",   # Ubuntu Archive Automatic Signing Key (2012) <ftpmaster@ubuntu.com>
        "2004": "40976EAF437D05B5",   # Ubuntu Archive Automatic Signing Key <ftpmaster@ubuntu.com>
    },
    "PureOS": {
        "repository": "2B4A53F2B41CE072",   # Purism PureOS Archive (PureOS Archive Signing Key) <sysadmin@puri.sm>
    },
}

SUITE_SETUP = {
    "stable": {
        "options": {
            "uploadable": False,
        },
    },
    "hotfix": {
        "migrates_to": "stable",
    },
    "testing": {
        "migrates_to": "stable",
        "options": {
            "uploadable": False,
        },
    },
    "unstable": {
        "migrates_to": "testing",
        "options": {
            "build_keyring_package": True,
        },
    },
    "snapshot": {
        "options": {
            "experimental": True,
        },
    },
    "experimental": {
        "options": {
            "experimental": True,
            "but_automatic_upgrades": False,
        },
    },
}

SETUP = {
    #: Default (Debian bullseye and up, Ubuntu groovy and up) lintian options. Overwrite in codename sections if needed.
    "lintian_options": {
        "common": ["--suppress-tags", "bad-distribution-in-changes-file"],
        #: Lintian "warnfail" option history:
        #:  * Debian (stretch)  2.5.50: Deprecates '--fail-on-warnings'.
        #:  * Debian (bullseye) 2.16.0: Drops support for '--fail-on-warnings' (NO WAY TO DO "WARNFAIL").
        #:  * Debian (bullseye) 2.77.0: Introduces '--fail-on'.
        #:  * Ubuntu (focal)    2.62.0: Release that does not have either (NO WAY TO DO "WARNFAIL").
        #:  * Ubuntu (groovy)   2.89.0: 1st release >= 2.77.
        #: Conclusion:
        #:  * Debian >=bullseye needs '--fail-on error,warning', below needs '--fail-on-warnings'.
        #:  * Ubuntu >=groovy needs '--fail-on error,warning', below needs '--fail-on-warnings'.
        #:  * Ubuntu focal just can't do it.
        "warnfail": ["--fail-on", "error,warning"],
    },

    "origin": {
        "Debian": {
            #: "Usually used" paths on a mirror. Can be used to guess individual archive URLS from a base mirror URL.
            "archive_paths": ["debian", "debian-security", "debian-ports", "debian-archive/debian", "debian-archive/debian-security", "debian-archive/debian-backports"],

            #: Canonical (internet) archives
            "archive": [
                "http://ftp.debian.org/debian/",                 # Debian (release, updates, proposed-updates and backports)
                "http://deb.debian.org/debian/",                 # alternate: CDN

                "http://security.debian.org/debian-security/",   # Debian Security (release/updates)
                "http://deb.debian.org/debian-security/",        # alternate: CDN

                "http://archive.debian.org/debian/",             # Archived Debian Releases
                "http://archive.debian.org/debian-security/",    # Archived Debian Security
                "http://archive.debian.org/debian-backports/",   # Archived Debian Backports
            ],
            "components": ["main", "contrib", "non-free", "non-free-firmware"],

            #: Security: Debian uses ``<codename>-security`` since "bullseye". Before that, it used strange ``<codename>/updates``.
            "security_codename_regex": re.compile(r"(^[a-z]+-security$|^[a-z]+/updates$)"),

            "codename": {
                "sid": {
                    "apt_keys": [APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                    "sources": {
                        "sid": {},
                    },
                },
                "trixie": {
                    "apt_keys": [APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                    "sources": {
                        "trixie": {},
                        "trixie-security": {
                            "apt_keys": [APT_KEYS["Debian"]["bullseye"]["security"], APT_KEYS["Debian"]["bookworm"]["security"]],
                            "options": {
                                "extra_options": "X-Remove-From-Component: updates/"
                            },
                        },
                        "trixie-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                },
                "bookworm": {
                    "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["release"]],
                    "sources": {
                        "bookworm": {},
                        "bookworm-security": {
                            "apt_keys": [APT_KEYS["Debian"]["bullseye"]["security"], APT_KEYS["Debian"]["bookworm"]["security"]],
                            "options": {
                                "extra_options": "X-Remove-From-Component: updates/"
                            },
                        },
                        "bookworm-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                        "bookworm-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                },
                "bullseye": {
                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bullseye"]["release"]],
                    "sources": {
                        "bullseye": {},
                        "bullseye-security": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["security"], APT_KEYS["Debian"]["bullseye"]["security"]],
                            "options": {
                                "extra_options": "X-Remove-From-Component: updates/",
                            },
                        },
                        "bullseye-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                        "bullseye-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                },
                "buster": {
                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["buster"]["release"], APT_KEYS["Debian"]["bullseye"]["archive"]],
                    "sources": {
                        "buster": {},
                        "buster/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["security"], APT_KEYS["Debian"]["bullseye"]["security"]],
                            "options": {
                                "extra_options": ("Codename: buster\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/"),
                            },
                        },
                        "buster-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                        "buster-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["bookworm"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "stretch": {
                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["stretch"]["archive"], APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"], APT_KEYS["Debian"]["stretch"]["release"]],
                    "sources": {
                        "stretch": {},
                        "stretch/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["stretch"]["security"], APT_KEYS["Debian"]["buster"]["security"]],
                            "options": {
                                "extra_options": ("Codename: stretch\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/"),
                            },
                        },
                        "stretch-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"]],
                            "origin": "Debian Backports",
                        },
                        "stretch-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["buster"]["archive"], APT_KEYS["Debian"]["bullseye"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "jessie": {
                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["release"], APT_KEYS["Debian"]["jessie"]["archive"]],
                    "sources": {
                        "jessie": {},
                        "jessie/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["jessie"]["security"], APT_KEYS["Debian"]["stretch"]["security"]],
                            "options": {
                                "extra_options": ("Codename: jessie\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/"),
                            },
                        },
                        "jessie-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["archive"]],
                            "origin": "Debian Backports",
                            "options": {
                                "extra_options": "X-Check-Valid-Until: no",
                            },
                        },
                        "jessie-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["archive"]],
                            "origin": "Debian Backports",
                            "options": {
                                "extra_options": "X-Check-Valid-Until: no",
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            # jessie sources 'archive.debian.org' and 'ftp.debian.org' are not the same:
                            # * jessie from archive.debian.org needs this setting (as the Release file is signed with an expired key, and jessie's apt just won't work).
                            # * jessie from ftp.debian.org (was left there for LTS for 'amd64 armel armhf i386' only) would work w/o that flag, as it is signed differently.
                            # However, both archives will match the jessie source, and both could potentially be used.
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-allow-unauthenticated apt-setup apt-key-binary apt-update ccache eatmydata",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "wheezy": {
                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["wheezy"]["release"], APT_KEYS["Debian"]["jessie"]["archive"]],
                    "sources": {
                        "wheezy": {},
                        "wheezy/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["security"]],
                            "options": {
                                "extra_options": ("Codename: wheezy\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/\n"
                                                  "X-Check-Valid-Until: no"),
                            },
                        },
                        "wheezy-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["archive"]],
                            "origin": "Debian Backports",
                        },
                        "wheezy-backports-sloppy": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"], APT_KEYS["Debian"]["jessie"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-allow-unauthenticated apt-setup apt-key-binary apt-update ccache eatmydata",
                        },
                    },
                    "chroot": {
                        "options": {
                            "extra_options": "Debootstrap-Extra-Options: --include=realpath",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "squeeze": {
                    "broken": "Only works with sbuild < 0.84",

                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["squeeze"]["archive"], APT_KEYS["Debian"]["squeeze"]["release"]],
                    "sources": {
                        "squeeze": {},
                        "squeeze/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["squeeze"]["archive"], APT_KEYS["Debian"]["jessie"]["security"]],
                            "options": {
                                "extra_options": ("Codename: squeeze\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/\n"
                                                  "X-Check-Valid-Until: no"),
                            },
                        },
                        "squeeze-lts": {
                            "apt_keys": [APT_KEYS["Debian"]["wheezy"]["archive"]],
                        },
                        "squeeze-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["squeeze"]["archive"], APT_KEYS["Debian"]["wheezy"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                    "distribution": {
                        "options": {
                            "build_dep_resolver": 0,  # util.models().Distribution.RESOLVER_APT
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-no-check-valid-until apt-allow-unauthenticated apt-setup apt-key-add apt-update ccache eatmydata",
                        },
                    },
                    "chroot": {
                        "options": {
                            "extra_options": "Debootstrap-Extra-Options: --include=realpath",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "lenny": {
                    "broken": "Cannot build test packages (only dh7 available)",

                    "components": ["main", "contrib", "non-free"],
                    "apt_keys": [APT_KEYS["Debian"]["squeeze"]["archive"], APT_KEYS["Debian"]["lenny"]["release"]],
                    "sources": {
                        "lenny": {},
                        "lenny/updates": {
                            "apt_keys": [APT_KEYS["Debian"]["lenny"]["archive"], APT_KEYS["Debian"]["jessie"]["security"]],
                            "options": {
                                "extra_options": ("Codename: lenny\n"
                                                  "Label: Debian-Security\n"
                                                  "X-Remove-From-Component: updates/\n"
                                                  "X-Check-Valid-Until: no"),
                            },
                        },
                        "lenny-backports": {
                            "apt_keys": [APT_KEYS["Debian"]["squeeze"]["archive"]],
                            "origin": "Debian Backports",
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-no-check-valid-until apt-allow-unauthenticated apt-setup apt-key-add apt-update ccache eatmydata",
                        },
                    },
                    "chroot": {
                        "options": {
                            "extra_options": ("Debootstrap-Command: /usr/sbin/mini-buildd-debootstrap-uname-2.6\n"
                                              "Debootstrap-Extra-Options: --include=realpath"),
                        },
                    },
                    "lintian_options": {
                        "common": [],
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
            },
        },
        #: Ubuntu (https://wiki.ubuntu.com/Releases)
        #:
        #: Keep all active LTS and the four most recent releases.
        "Ubuntu": {
            "archive_paths": ["ubuntu", "ubuntu-old"],
            "archive": [
                "http://archive.ubuntu.com/ubuntu/",       # Releases
                "http://security.ubuntu.com/ubuntu/",      # Security
                "http://old-releases.ubuntu.com/ubuntu/",  # Older Releases
            ],
            "components": ["main", "universe", "restricted", "multiverse"],

            #: Security: Ubuntu uses ``<codename>-security``.
            "security_codename_regex": re.compile(r"^[a-z]+-security$"),

            "codename": {
                "lunar": {  # lunar: 23.04
                    "broken": "No support in ``debmirror`` yet (May 2023)",

                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "lunar": {},
                        "lunar-security": {
                            "options": {
                                "extra_options": ("Codename: lunar\n"
                                                  "Suite: lunar-security"),
                            },
                        },
                        "lunar-backports": {
                            "options": {
                                "extra_options": ("Codename: lunar\n"
                                                  "Suite: lunar-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "kinetic": {  # kinetic: 22.10
                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "kinetic": {},
                        "kinetic-security": {
                            "options": {
                                "extra_options": ("Codename: kinetic\n"
                                                  "Suite: kinetic-security"),
                            },
                        },
                        "kinetic-backports": {
                            "options": {
                                "extra_options": ("Codename: kinetic\n"
                                                  "Suite: kinetic-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "jammy": {  # jammy: 22.04 LTS 2027
                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "jammy": {},
                        "jammy-security": {
                            "options": {
                                "extra_options": ("Codename: jammy\n"
                                                  "Suite: jammy-security"),
                            },
                        },
                        "jammy-backports": {
                            "options": {
                                "extra_options": ("Codename: jammy\n"
                                                  "Suite: jammy-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "impish": {  # impish: 21.10
                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "impish": {},
                        "impish-security": {
                            "options": {
                                "extra_options": ("Codename: impish\n"
                                                  "Suite: impish-security"),
                            },
                        },
                        "impish-backports": {
                            "options": {
                                "extra_options": ("Codename: impish\n"
                                                  "Suite: impish-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "hirsute": {  # hirsute: 21.04
                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "hirsute": {},
                        "hirsute-security": {
                            "options": {
                                "extra_options": ("Codename: hirsute\n"
                                                  "Suite: hirsute-security"),
                            },
                        },
                        "hirsute-backports": {
                            "options": {
                                "extra_options": ("Codename: hirsute\n"
                                                  "Suite: hirsute-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "groovy": {  # groovy: 20.10
                    "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "groovy": {},
                        "groovy-security": {
                            "options": {
                                "extra_options": ("Codename: groovy\n"
                                                  "Suite: groovy-security"),
                            },
                        },
                        "groovy-backports": {
                            "options": {
                                "extra_options": ("Codename: groovy\n"
                                                  "Suite: groovy-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        "arch_optional": ["i386"],
                    },
                },
                "focal": {  # focal: 20.04 LTS 2025 ESM 2030
                    "apt_keys": [APT_KEYS["Ubuntu"]["2012"], APT_KEYS["Ubuntu"]["2018"]],
                    "sources": {
                        "focal": {},
                        "focal-security": {
                            "options": {
                                "extra_options": ("Codename: focal\n"
                                                  "Suite: focal-security"),
                            },
                        },
                        "focal-backports": {
                            "options": {
                                "extra_options": ("Codename: focal\n"
                                                  "Suite: focal-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                        # Ubuntu drops i386 (starting with focal)
                        # 'focal' still has i386 repo (debootrap still works), so i386 is still picked
                        # by mini-buildd's setup. However, builds no longer work (default resolver
                        # aptitude missing).
                        # Choosing "apt" resolver would master this hurdle, but this would also change
                        # the default resolver for amd64. Imho best solution for mini-buildd's setup
                        # is to make this arch optional.
                        # Ref: https://discourse.ubuntu.com/t/community-process-for-32-bit-compatibility/12598?u=d0od
                        "arch_optional": ["i386"],
                    },
                    "lintian_options": {
                        "warnfail": [],
                    },
                },
                "bionic": {  # bionic: 18.04 LTS 2023 ESM 2028
                    "apt_keys": [APT_KEYS["Ubuntu"]["2012"]],
                    "sources": {
                        "bionic": {},
                        "bionic-security": {
                            "options": {
                                "extra_options": ("Codename: bionic\n"
                                                  "Suite: bionic-security"),
                            },
                        },
                        "bionic-backports": {
                            "options": {
                                "extra_options": ("Codename: bionic\n"
                                                  "Suite: bionic-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            # .. attention:: **compat**: ``ddeb`` reprepro workaround (:debbug:`730572`)
                            # Starting with cosmic (18.10), Ubuntu uses 'ddeb' as file appendix for automated debug packages.
                            # reprepro can't handle these yet -- so this is needed for a workaround.
                            "extra_options": "Deb-Build-Options: noddebs",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "xenial": {  # xenial: 16.04 LTS 2021 ESM 2026
                    "apt_keys": [APT_KEYS["Ubuntu"]["2004"], APT_KEYS["Ubuntu"]["2012"]],
                    "sources": {
                        "xenial": {},
                        "xenial-security": {
                            "options": {
                                "extra_options": ("Codename: xenial\n"
                                                  "Suite: xenial-security"),
                            },
                        },
                        "xenial-backports": {
                            "options": {
                                "extra_options": ("Codename: xenial\n"
                                                  "Suite: xenial-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-setup apt-key-add apt-update ccache eatmydata",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
                "trusty": {  # trusty: 14.04 LTS 2019 ESM 2024
                    "broken": "``upstart`` install fails in build chroots",

                    "apt_keys": [APT_KEYS["Ubuntu"]["2004"], APT_KEYS["Ubuntu"]["2012"]],
                    "sources": {
                        "trusty": {},
                        "trusty-security": {
                            "options": {
                                "extra_options": ("Codename: trusty\n"
                                                  "Suite: trusty-security"),
                            },
                        },
                        "trusty-backports": {
                            "options": {
                                "extra_options": ("Codename: trusty\n"
                                                  "Suite: trusty-backports"),
                            },
                        },
                    },
                    "distribution": {
                        "options": {
                            "extra_options": f"{sbuild.SETUP_BLOCKS.name}: apt-setup apt-key-add apt-update ccache eatmydata",
                        },
                    },
                    "chroot": {
                        "options": {
                            "extra_options": "Debootstrap-Extra-Options: --include=realpath",
                        },
                    },
                    "lintian_options": {
                        "warnfail": ["--fail-on-warnings"],
                    },
                },
            },
        },

        #: PureOS (https://pureos.net/)
        "PureOS": {
            "archive_paths": [],
            "archive": [
                "https://mirror.fsf.org/pureos/",
            ],
            "components": ["main"],

            #: Security: PureOS uses ``<codename>-security``.
            "security_codename_regex": re.compile(r"^[a-z]+-security$"),

            "codename": {
                "amber": {
                    "apt_keys": [APT_KEYS["PureOS"]["repository"]],
                    "sources": {
                        "amber": {},
                        "amber-security": {},
                        "amber-updates": {},
                    },
                },
            },
        },
    },
    "layout": {
        "Default": {
            "suites": SUITE_SETUP,
            "rollback": {"stable": 6, "hotfix": 4, "testing": 3, "unstable": 9, "snapshot": 12, "experimental": 6},
        },
        "Default (no rollbacks)": {
            "suites": SUITE_SETUP,
            "rollback": {},
        },
        "Debian Developer": {
            "suites": {s: c for s, c in SUITE_SETUP.items() if s in ["stable", "testing", "unstable", "experimental"]},
            "rollback": {},
            "options": {
                "mandatory_version_regex": ".*",
                "experimental_mandatory_version_regex": ".*",
                "extra_options": "Meta-Distributions: unstable=sid-unstable experimental=sid-experimental",
            }
        },
    },
}


def setup_origins():
    return SETUP["origin"].keys()


def setup_codenames():
    codenames = []
    for o in setup_origins():
        codenames.extend(SETUP["origin"][o]["codename"])
    return codenames


VENDOR_CODENAMES_FUNC = {
    "": lambda origin: [codename for codename in DISTRO_INFO[origin].supported() if codename in setup_codenames()],
    "lts": lambda origin: [codename for codename in DISTRO_INFO[origin].supported() + DISTRO_INFO[origin].mbd_lts() if codename in setup_codenames()],
    "all": lambda origin: [codename for codename, setup in SETUP["origin"][origin]["codename"].items() if setup.get("broken") is None],
}


def setup_codenames_from_origin(origin, modifier=""):
    try:
        return VENDOR_CODENAMES_FUNC[modifier](origin)
    except KeyError as e:
        raise util.HTTPBadRequest(f"Unknown codenames selector: '{origin}:{modifier}' (use '{'|'.join(setup_origins())}[:{'|'.join(VENDOR_CODENAMES_FUNC.keys())}]'") from e


def _idi_codenames():
    for di in DISTRO_INFO.values():
        yield from di.all


def di_codenames():
    return list(_idi_codenames())


def guess_origin(codename):
    """
    Guess Origin from codename

    >>> guess_origin("rex")  # only via distro info
    'Debian'
    >>> guess_origin("warty")  # only via distro info
    'Ubuntu'
    >>> guess_origin("bullseye")  # via setup
    'Debian'
    """
    for origin, setup in SETUP["origin"].items():
        if codename in setup["codename"]:
            return origin

        # Check if we can find codename in distro info
        di = DISTRO_INFO.get(origin)
        if di and codename in di.all:
            return origin
    return None


def is_base_source(origin, codename):
    """Heuristics (for setup) whether this is a base source"""
    return origin in SETUP["origin"] and re.match(r"^[a-z]+$", codename) and codename != "experimental"


#: List of Debian codenames using something ``3.1`` as main version (all <= squeeze)
DEBIAN_MAJOR_MINOR_CODENAMES = ["buzz", "rex", "bo", "hamm", "slink", "potato", "woody", "sarge", "etch", "lenny", "squeeze"]


def guess_codeversion(origin, codename, version):
    """
    Get recommended codeversion from origin/codename/version triple

    ``codename`` is essentially one number representing the release version, which can later be used as in
    mandatory version part, or for sorting codenames.

    ``version`` may be from a ``Release`` file (like '11.3' for bullseye), or from ``distro-info-data``
    (just '11'). If empty, ``~CODENAME`` is returned.

    Some heuristics (tailored for Debian and Ubuntu, but may work fine for other origins) are applied to
    produce a reasonable ``codeversion``.

    In Debian,
      - point release <= sarge had the 'M.PrN' syntax (with 3.1 being a major release).
      - point release in squeeze used 'M.0.N' syntax.
      - point releases for >= wheezy have the 'M.N' syntax (with 7.1 being a point release).
      - testing and unstable do not gave a version in Release and fall back to uppercase codename

    Ubuntu just uses YY.MM which we can use as-is.

    >>> guess_codeversion("Debian", "sarge", "3.1r8")
    '31'
    >>> guess_codeversion("Debian", "etch", "4.0r9")
    '40'
    >>> guess_codeversion("Debian", "squeeze", "6.0.6")
    '60'
    >>> guess_codeversion("Debian", "wheezy", "7.0")
    '7'
    >>> guess_codeversion("Debian", "wheezy", "7.1")
    '7'
    >>> guess_codeversion("Ubuntu", "quantal", "12.10")
    '1210'
    """
    if not version:
        return "~" + codename.upper()

    ver_split = version.split(".")

    def number(no):
        """Be sure to not fail (return empty str instead), and only use *starting* digits"""
        v = ver_split[no] if len(ver_split) > no else ""
        m = re.search(r"^(\d+)", v)
        return m.group(1) if m else ""

    if origin == "Debian" and codename not in DEBIAN_MAJOR_MINOR_CODENAMES:
        return number(0)  # Debian >= wheezy
    return f"{number(0)}{number(1)}"


@functools.total_ordering
class Codename():
    def __init__(self, codename, origin="", version=""):
        self.codename = codename
        self.origin = origin if origin else guess_origin(codename)

        di = DISTRO_INFO.get(self.origin)

        # Set or compute version (may be empty string)
        if not version and di is not None:
            r = di.mbd_release(codename)
            version = di.version(self.codename) if (r is not None and r.release is not None) else ""  # Version might be non-empty in distro-info-data even if not released

        # Guess codeversion
        self.codeversion = guess_codeversion(self.origin, self.codename, version)

        # Compute cmpversion
        try:
            if di is None:
                raise util.HTTPUnavailable("No distro info")

            version = di.version(codename)
            if not version:
                raise util.HTTPUnavailable("No version in distro info")

            self.cmpversion = debian.debian_support.Version(version.replace(" ", "+"))  # Some Ubuntu versions need " LTS"->"+LTS" to produce valid version strings
        except Exception as e:
            LOG.debug(f"No sort info for '{self.codename}', sorting last: {e}")
            self.cmpversion = debian.debian_support.Version("9999")

        self.setup = SETUP["origin"].get(self.origin, {}).get("codename", {}).get(self.codename)
        self.has_setup = self.setup is not None
        if not self.has_setup:
            self.setup = self._default_setup()

    def _default_setup(self):
        if self.origin == "Debian":
            return {
                "sources": {
                    self.codename: {},
                    f"{self.codename}-security": {
                        "options": {
                            "extra_options": "X-Remove-From-Component: updates/",
                        },
                    },
                    f"{self.codename}-backports": {
                        "origin": "Debian Backports",
                    },
                }
            }
        if self.origin == "Ubuntu":
            return {
                "apt_keys": [APT_KEYS["Ubuntu"]["2018"]],
                "sources": {
                    self.codename: {},
                    f"{self.codename}-security": {
                        "options": {
                            "extra_options": f"Codename: {self.codename}\nSuite: {self.codename}-security",
                        },
                    },
                    f"{self.codename}-backports": {
                        "options": {
                            "extra_options": f"Codename: {self.codename}\nSuite: {self.codename}-backports",
                        },
                    },
                },
                "distribution": {
                    "options": {
                        "extra_options": "Deb-Build-Options: noddebs",
                    },
                    "arch_optional": ["i386"],
                }
            }
        return {}

    def setup_lintian_options(self, kind):
        """
        Get lintian options from SETUP
        """
        return util.mdget(self.setup, ["lintian_options", kind], SETUP["lintian_options"][kind])

    def setup_sources(self):
        return self.setup.get("sources", {}).keys()

    def setup_origin(self, source_codename):
        return util.mdget(self.setup, ["sources", source_codename, "origin"], self.origin)

    def setup_apt_keys(self, source_codename):
        return util.mdget(self.setup, ["sources", source_codename, "apt_keys"], self.setup.get("apt_keys", []))

    def setup_options(self, source_codename):
        return util.mdget(self.setup, ["sources", source_codename, "options"], {})

    def setup_distribution_options(self):
        return util.mdget(self.setup, ["distribution", "options"], {})

    def setup_chroot_options(self):
        return util.mdget(self.setup, ["chroot", "options"], {})

    def setup_is_security(self, source_codename):
        regex = util.mdget(SETUP, ["origin", self.origin, "security_codename_regex"])
        return regex is not None and regex.match(source_codename)

    def setup_arch_optional(self):
        return util.mdget(self.setup, ["distribution", "arch_optional"], [])

    def setup_components(self):
        return util.mdget(self.setup, ["components"], util.mdget(SETUP, ["origin", self.origin, "components"], ["main"]))

    def __str__(self):
        return f"{self.origin} '{self.codename}' ({self.codeversion})"

    def __hash__(self):
        return hash(self.codename)

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.origin == other.origin and self.codename == other.codename
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, self.__class__):
            if self.origin == other.origin:
                return self.cmpversion < other.cmpversion
            return self.origin and other.origin and self.origin < other.origin
        return NotImplemented


class SbuildCheck():
    """
    Generic support for sbuild checks (lintian, piuparts, autopkgtest)

    >>> SbuildCheck("lindian", "disabled")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Unknown sbuild checker: lindian (valid options: lintian,piuparts,autopkgtest) (HTTP 400 Bad request syntax or unsupported method)

    >>> SbuildCheck("lintian", "warnfall")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Unknown sbuild check mode: warnfall (valid options: DISABLED,IGNORE,ERRFAIL,WARNFAIL) (HTTP 400 Bad request syntax or unsupported method)

    >>> sc = SbuildCheck("lintian", "warnfail")
    >>> sc.checker
    'lintian'
    >>> sc.mode
    <Mode.WARNFAIL: 3>
    """

    class Mode(enum.Enum):
        DISABLED = 0
        IGNORE = 1
        ERRFAIL = 2
        WARNFAIL = 3

        def __str__(self):
            return {
                self.DISABLED: "Don't run check",
                self.IGNORE: "Run check but ignore results",
                self.ERRFAIL: "Run check and fail on errors",
                self.WARNFAIL: "Run check and fail on warnings",
            }[self]

    CHECKERS = ["lintian", "piuparts", "autopkgtest"]
    CHOICES = [(mode.value, mode.name) for mode in Mode]

    #: From sbuild source code: We may expect these textual statuses:
    STATUSES_PASS = ["pass", "info"]
    STATUSES_WARN = ["warn", "no tests"]
    STATUSES_FAIL = ["error", "fail"]

    def __init__(self, checker, mode):
        if checker not in self.CHECKERS:
            raise util.HTTPBadRequest(f"Unknown sbuild checker: {checker} (valid options: {','.join(self.CHECKERS)})")
        self.checker = checker
        try:
            self.mode = self.Mode[mode.upper() if isinstance(mode, str) else mode]
        except KeyError as e:
            raise util.HTTPBadRequest(f"Unknown sbuild check mode: {mode} (valid options: {','.join([mode.name for mode in self.Mode])})") from e

    @classmethod
    def desc(cls):
        return "Mode to control if a check should prevent package installation (for non-experimental suites)."

    @classmethod
    def usage(cls):
        return ", ".join([f"'{mode.name}' ({mode})" for mode in cls.Mode])

    def check(self, status, ignore=False):
        """Check if status is ok in this mode"""
        return \
            ignore or \
            status in self.STATUSES_PASS or \
            self.mode in [self.Mode.DISABLED, self.Mode.IGNORE] or \
            (status in self.STATUSES_WARN and self.mode is not self.Mode.WARNFAIL)

    def lintian_options(self, codename):
        _codename = Codename(codename)
        options = _codename.setup_lintian_options("common")
        if self.mode == self.Mode.WARNFAIL:
            options += _codename.setup_lintian_options("warnfail")
        return util.PyCompat.shlex_join(options)


class Dist():
    """
    A mini-buildd distribution string

    Normal distribution:

    >>> d = Dist("squeeze-test-stable")
    >>> d.codename, d.repository, d.suite
    ('squeeze', 'test', 'stable')
    >>> d.get()
    'squeeze-test-stable'

    Rollback distribution:

    >>> d = Dist("squeeze-test-stable-rollback5")
    >>> d.is_rollback
    True
    >>> d.get(rollback=False)
    'squeeze-test-stable'
    >>> d.codename, d.repository, d.suite, d.rollback
    ('squeeze', 'test', 'stable', 'rollback5')
    >>> d.get()
    'squeeze-test-stable-rollback5'
    >>> d.rollback_no
    5

    Malformed distributions:

    >>> Dist("-squeeze-stable")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Malformed distribution '-squeeze-stable': Must be '<codename>-<repoid>-<suite>[-rollback<n>]' (HTTP 400 Bad request syntax or unsupported method)

    >>> Dist("squeeze--stable")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Malformed distribution 'squeeze--stable': Must be '<codename>-<repoid>-<suite>[-rollback<n>]' (HTTP 400 Bad request syntax or unsupported method)

    >>> Dist("squeeze-test-stable-")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Malformed distribution 'squeeze-test-stable-': Must be '<codename>-<repoid>-<suite>[-rollback<n>]' (HTTP 400 Bad request syntax or unsupported method)

    >>> Dist("squeeze-test-stable-rollback")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Malformed distribution 'squeeze-test-stable-rollback': Must be '<codename>-<repoid>-<suite>[-rollback<n>]' (HTTP 400 Bad request syntax or unsupported method)

    >>> Dist("squeeze-test-stable-rolback0")  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    util.HTTPBadRequest: Malformed distribution 'squeeze-test-stable-rolback0': Must be '<codename>-<repoid>-<suite>[-rollback<n>]' (HTTP 400 Bad request syntax or unsupported method)
    """

    _REGEX = re.compile(r"^\w+-\w+-\w+?(-rollback\d+)?$")

    def __init__(self, diststr):
        if not self._REGEX.match(diststr):
            raise util.HTTPBadRequest(f"Malformed distribution '{diststr}': Must be '<codename>-<repoid>-<suite>[-rollback<n>]'")

        self.diststr = diststr
        self._dsplit = self.diststr.split("-")
        self.codename = self._dsplit[0]
        self.repository = self._dsplit[1]
        self.suite = self._dsplit[2]
        self.is_rollback = len(self._dsplit) == 4
        self.rollback = self._dsplit[3] if self.is_rollback else None
        self.rollback_no = int(re.sub(r"\D", "", self.rollback)) if self.rollback else None

    def get(self, rollback=True):
        return "-".join(self._dsplit) if rollback else "-".join(self._dsplit[:3])


if __name__ == "__main__":
    for DI in [DebianDistroInfo(), UbuntuDistroInfo()]:
        for C in DI.all:
            print(Codename(C))
    print(setup_origins())
    print(setup_codenames())
    print(setup_codenames_from_origin("Debian"))
    print(setup_codenames_from_origin("Debian", "all"))
    print(setup_codenames_from_origin("Debian", "lts"))
    print(di_codenames())

    c = Codename("jdaskljk")
    cl = [Codename(c) for c in DebianDistroInfo().all]
    print([f"{c.codename} {c.cmpversion}" for c in sorted(cl + [c])])
    cl = [Codename(c) for c in UbuntuDistroInfo().all]
    print([f"{c.codename} {c.cmpversion}" for c in sorted(cl + [c])])
