"""Tests that publish rpm plugin repositories."""

import unittest

from datetime import datetime
from tempfile import NamedTemporaryFile
from urllib.parse import urljoin

import requests
from html.parser import HTMLParser
from productmd.treeinfo import TreeInfo

from pulp_smash import api, config
from pulp_smash.pulp3.bindings import delete_orphans
from pulp_smash.pulp3.utils import (
    gen_repo,
    get_added_content_summary,
    get_content_summary,
)

from pulp_rpm.tests.functional.constants import (
    RPM_DISTRIBUTION_PATH,
    RPM_PACKAGE_CONTENT_NAME,
    RPM_KICKSTART_FIXTURE_URL,
    RPM_PUBLICATION_PATH,
    RPM_REMOTE_PATH,
    RPM_REPO_PATH,
    CENTOS7_URL,
    CENTOS8_STREAM_APPSTREAM_URL,
    CENTOS8_STREAM_BASEOS_URL,
    EPEL7_URL,
)
from pulp_rpm.tests.functional.utils import gen_rpm_remote
from pulp_rpm.tests.functional.utils import set_up_module as setUpModule  # noqa:F401


class PackagesHtmlParser(HTMLParser):
    """HTML parser that looks for the first link to a file.

    This parser look for the first link that doesn't have a trailing slash. It then sets the
    package_href attribute to the value of the href.
    """

    package_href = None

    def handle_starttag(self, tag, attrs):
        """Looks for a tags that don't end with a slash."""
        if not self.package_href:
            if tag == "a" and not attrs[0][1].endswith("/"):
                self.package_href = attrs[0][1]
        else:
            super().handle_starttag(tag, attrs)


class PublishTestCase(unittest.TestCase):
    """Publish repositories with the rpm plugin."""

    @classmethod
    def setUpClass(cls):
        """Create class-wide variables."""
        cls.cfg = config.get_config()
        cls.client = api.Client(cls.cfg, api.json_handler)

        delete_orphans()

    def parse_date_from_string(self, s, parse_format="%Y-%m-%dT%H:%M:%S.%fZ"):
        """Parse string to datetime object.

        :param s: str like '2018-11-18T21:03:32.493697Z'
        :param parse_format: str defaults to %Y-%m-%dT%H:%M:%S.%fZ
        :return: datetime.datetime
        """
        return datetime.strptime(s, parse_format)

    def rpm_publish(self, url=RPM_KICKSTART_FIXTURE_URL, policy="on_demand"):
        """Publish repositories with the rpm plugin.

        This test targets the following issue:
        `Pulp #5630 <https://pulp.plan.io/issues/5630>`_.

        In order to sync a repository a remote has to be associated within
        this repository. When a repository is created this version field is set
        as None. After a sync the repository version is updated.

        Do the following:

        1. Create a repository and a remote.
        2. Assert that repository version is None.
        3. Sync the remote.
        4. Assert that repository version is not None.
        5. Assert that distribution_tree units were added and are present in the repo.
        6. Publish
        """
        delete_orphans()
        repo = self.client.post(RPM_REPO_PATH, gen_repo())
        self.addCleanup(self.client.delete, repo["pulp_href"])

        # Create a remote with the standard test fixture url.
        body = gen_rpm_remote(url=url, policy=policy)
        remote = self.client.post(RPM_REMOTE_PATH, body)
        self.addCleanup(self.client.delete, remote["pulp_href"])

        # Sync the repository.
        self.assertEqual(repo["latest_version_href"], f"{repo['pulp_href']}versions/0/")
        data = {"remote": remote["pulp_href"]}
        response = self.client.using_handler(api.json_handler).post(
            urljoin(repo["pulp_href"], "sync/"), data
        )
        sync_task = self.client.get(response["task"])
        created_at = self.parse_date_from_string(sync_task["pulp_created"])
        started_at = self.parse_date_from_string(sync_task["started_at"])
        finished_at = self.parse_date_from_string(sync_task["finished_at"])
        task_duration = finished_at - started_at
        waiting_time = started_at - created_at
        print(
            "\n->     Sync => Waiting time (s): {wait} | Service time (s): {service}".format(
                wait=waiting_time.total_seconds(), service=task_duration.total_seconds()
            )
        )

        repo = self.client.get(repo["pulp_href"])

        # Check that we have the correct content counts.
        self.assertIsNotNone(repo["latest_version_href"])

        self.assertIn(
            RPM_PACKAGE_CONTENT_NAME,
            get_content_summary(repo).keys(),
        )
        self.assertIn(
            RPM_PACKAGE_CONTENT_NAME,
            get_added_content_summary(repo).keys(),
        )

        repo = self.client.get(repo["pulp_href"])

        # Publishing
        body = {"repository": repo["pulp_href"]}
        response = self.client.using_handler(api.json_handler).post(RPM_PUBLICATION_PATH, body)
        publish_task = self.client.get(response["task"])
        created_at = self.parse_date_from_string(publish_task["pulp_created"])
        started_at = self.parse_date_from_string(publish_task["started_at"])
        finished_at = self.parse_date_from_string(publish_task["finished_at"])
        task_duration = finished_at - started_at
        waiting_time = started_at - created_at
        print(
            "\n->     Publish => Waiting time (s): {wait} | Service time (s): {service}".format(
                wait=waiting_time.total_seconds(), service=task_duration.total_seconds()
            )
        )
        return publish_task["created_resources"][0]

    def test_epel7(self):
        """Publish EPEL 7."""
        self.rpm_publish(url=EPEL7_URL)

    def test_centos7(self):
        """Publish CentOS 7."""
        self.rpm_publish(url=CENTOS7_URL)

    def test_centos_8stream_baseos(self):
        """Publish CentOS 8 BaseOS."""
        publication_href = self.rpm_publish(url=CENTOS8_STREAM_BASEOS_URL)
        # Test that the .treeinfo file is available and AppStream sub-repo is published correctly
        body = {"publication": publication_href, "name": "centos8", "base_path": "centos8"}
        response = self.client.using_handler(api.json_handler).post(RPM_DISTRIBUTION_PATH, body)
        distribution_task = self.client.get(response["task"])
        distribution_href = distribution_task["created_resources"][0]
        self.addCleanup(self.client.delete, distribution_href)
        distribution = self.client.get(distribution_href)
        treeinfo_file = requests.get(urljoin(distribution["base_url"], ".treeinfo")).content
        treeinfo = TreeInfo()
        with NamedTemporaryFile("wb") as temp_file:
            temp_file.write(treeinfo_file)
            temp_file.flush()
            treeinfo.load(f=temp_file.name)
        for variant_name, variant in treeinfo.variants.variants.items():
            if variant_name == "BaseOS":
                self.assertEqual(variant.paths.repository, ".")
                self.assertEqual(variant.paths.packages, "Packages")
            elif variant_name == "AppStream":
                self.assertEqual(variant.paths.repository, "AppStream")
                self.assertEqual(variant.paths.packages, "AppStream/Packages")
                # Find the first package in the 'AppStream/Packages/a/' directory and download it
                parser = PackagesHtmlParser()
                a_packages_href = urljoin(
                    distribution["base_url"], "{}/a/".format(variant.paths.packages)
                )

                a_packages_listing = requests.get(a_packages_href).text
                parser.feed(a_packages_listing)
                full_package_path = urljoin(a_packages_href, parser.package_href)
                assert requests.get(full_package_path).status_code == 200

    def test_centos8_appstream(self):
        """Publish CentOS 8 AppStream."""
        self.rpm_publish(url=CENTOS8_STREAM_APPSTREAM_URL)

    def test_centos8_baseos(self):
        """Publish CentOS 8 BaseOS."""
        self.rpm_publish(url=CENTOS8_STREAM_BASEOS_URL)
