# Source code of the Ubuntu-it website
# Copyright (C) 2023 shadMod
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
import toml
import json
import requests

from lxml import html
from urllib.request import urlopen
from urllib.error import HTTPError


class UbuntuRelease:
    def __init__(self, path_url, ignore_version=None):
        self.path_url = path_url
        self.ignore_version = ignore_version

    def get_table_rows(self, only_data: bool = True):
        """
        :param only_data:   to check you want to ignore the table header
        """

        # get requests
        res = requests.get(self.path_url)
        # check requests status code
        status = res.status_code
        if status != 200:
            raise Exception(f"Error requests - status: {status}")
        # get all table rows
        table_rows = html.fromstring(res.content).xpath('//pre//text()')
        # if only_data is True ignore table header
        if only_data is True:
            return table_rows[8:]
        # else get all table
        return table_rows

    @property
    def get_list_version(self) -> list:
        # init clean row and row_list
        row = []
        row_list = []
        # get table_rows without header
        for val in self.get_table_rows():
            val = val.strip()
            if val:
                if 'Ubuntu ' not in val:
                    row.append(val.replace("/", ""))
                else:
                    row.append(val)

            if len(row) == 2:
                if not row[0].split(".")[0].isnumeric():
                    continue

                if 'LTS' in row[1]:
                    typology = "LTS"
                else:
                    typology = "interim"

                name = row[1].split("(")[1].replace(")", "")
                short = name.split()[0].lower()
                row_list.append((row[0], short, name, typology))
                row = []

        # the lambda is just out of scruple
        return sorted(row_list, key=lambda x: x[0], reverse=True)


class CompileVersion:
    """
    :param SUPPORT_5:       set distro with 5 years support (else are 3 years)
    :param INVERT_RELEASES: set if you want invert releases in template render

    :param path_url:        url to get version list
    :param path_out:        path where the toml file will be written
    :param constants:       constants path
    :param ignore_version:  list of tags versions to be ignored
    """

    SUPPORT_5 = ["desktop", "live-server"]
    INVERT_RELEASES = ["live-server"]

    def __init__(
            self,
            path_url: str = "https://releases.ubuntu.com/",
            path_out: str = "../../data/downloads.toml",
            downloads: str = "./assets/downloads.json",
            info: str = "./assets/info.json",
            ignore_version: list = None,
    ):
        self.path_url = path_url
        self.path_out = path_out
        self.downloads = downloads
        self.info = info
        self.ignore_version = ignore_version if ignore_version else []

        self.list_version = UbuntuRelease(
            path_url=self.path_url,
            ignore_version=self.ignore_version,
        ).get_list_version

    @property
    def data_sources(self) -> list:
        return [
            (
                "releases-ubuntu",
                "https://releases.ubuntu.com/{codename}/ubuntu-{version}-{distro}-{arch}.iso",
            ),
            (
                "cdimages-ubuntu",
                "https://cdimages.ubuntu.com/releases/{codename}/release/ubuntu-{version}-{distro}-{arch}.iso",
            ),
            (
                "cdimages-derivatives",
                "https://cdimages.ubuntu.com/{distro}/releases/{codename}/release/{distro}-{version}-desktop-{arch}.iso",
            ),
            (
                "cdimages-studio",
                "https://cdimages.ubuntu.com/{distro}/releases/{codename}/release/{distro}-{version}-dvd-{arch}.iso",
            ),
        ]

    @property
    def ver_latest(self) -> tuple:
        return max(self.list_version)

    @property
    def ver_latest_lts(self) -> tuple:
        return max(self.ver_lts_list)

    @property
    def ver_interim_list(self) -> list:
        return [x for x in self.list_version if x[3] == 'interim']

    @property
    def ver_lts_list(self) -> list:
        return [x for x in self.list_version if x[3] == 'LTS']

    def compile_download(self) -> None:
        # init data with all data downloads
        with open(self.downloads, "r") as fn:
            data = json.load(fn)
        # put all data from info
        with open(self.info, "r") as fn:
            for distro, info in json.load(fn).items():
                data['distros'][distro]['info'] = info

        # write all distros and relative archs
        for key in data["distros"]:
            # get single distro
            distro = data["distros"][key]
            # set up the support years of the LTS
            distro["lts-support-years"] = (
                5 if key in self.SUPPORT_5 else 3
            )
            # put default arch: 'amd64'
            releases = {
                "amd64": self.populate_archs_releases(key, "amd64", distro["releases"])
            }
            # check and adding any other archs
            add_archs = distro.get("add_archs")
            if add_archs:
                for _arch in add_archs:
                    releases[_arch] = self.populate_archs_releases(key, _arch, distro["releases"])
            # put all releases in archs key
            distro["archs"] = releases

        # write sources
        data["sources"] = {}
        for key, path in self.data_sources:
            data["sources"][key] = {"https": path, "torrent": path + ".torrent"}

        # write download.toml
        with open(self.path_out, "w") as out:
            toml.dump(data, out)

    def populate_archs_releases(self, distro: str, arch: str, select) -> dict:
        """
        :param distro:  name of the distro
        :param arch:    name of the architecture
        :param select:  list of version required
        """

        rels = {}
        res, typology = self.get_releases(self.list_version, distro, arch)
        if typology == "interim" and "interim" in select:
            rels['interim'] = res
            if "lts" in select:
                res, _ = self.get_releases(self.ver_lts_list, distro, arch)
                rels['lts'] = res
        else:
            res, _ = self.get_releases(self.ver_lts_list, distro, arch)
            rels['interim'] = res

        if not rels:
            raise Exception("No archs releases")
        return rels

    def get_releases(self, data: list, distro: str, arch: str) -> (dict, str):
        """
        :param data:    list of all versions
        :param distro:  name of the distro
        :param arch:    name of the architecture
        """

        host = None
        for version, codename, name, typology in data:
            host = None
            for http, path in self.data_sources:
                try:
                    conn = urlopen(
                        path.format(
                            distro=distro, codename=codename, version=version, arch=arch
                        )
                    )
                except HTTPError:
                    pass
                except ValueError:
                    pass
                else:
                    host = http
                    conn.close()
                    break
            if host:
                return {
                    "version": version,
                    "codename": codename,
                    "host": host,
                    "lts": False if typology == "interim" else True,
                }, typology

        if host is None:
            raise Exception(f"No correct host for {distro}")
