# Copyright (c) 2024 Cisco Systems, Inc. and its affiliates
# All rights reserved.

from unittest import mock

import requests

from soufi import exceptions
from soufi.finder import SourceType
from soufi.finders import crate
from soufi.testing import base


class TestCrateFinder(base.TestCase):
    def make_finder(self, name=None, version=None, index=None):
        if name is None:
            name = self.factory.make_string("name")
        if version is None:
            version = self.factory.make_string("version")
        kwargs = dict(name=name, version=version, s_type=SourceType.crate)
        if index is not None:
            kwargs["index"] = index
        return crate.CrateFinder(**kwargs)

    def test_get_source_url(self):
        finder = self.make_finder()
        get_index_dl = self.patch(finder, "get_index_dl")
        index_dl = self.factory.make_url()
        get_index_dl.return_value = index_dl

        head = self.patch_head_with_response(requests.codes.ok)
        found_url = finder.get_source_url()
        expected_url = (
            f"{index_dl}/{finder.name}/{finder.name}-{finder.version}.crate"
        )
        self.assertEqual(expected_url, found_url)
        call = mock.call(
            expected_url,
            timeout=30,
            allow_redirects=True,
        )
        self.assertIn(call, head.call_args_list)

    def test_get_source_url_source_not_found(self):
        finder = self.make_finder()
        self.patch(
            finder, "get_index_dl"
        ).return_value = self.factory.make_url()
        # Crates index returns a 403 when the crate is not found. ¯\_(ツ)_/¯
        self.patch_head_with_response(requests.codes.forbidden)
        self.assertRaises(exceptions.SourceNotFound, finder.get_source_url)

    def test_get_index_dl(self):
        index = self.factory.make_url()
        finder = self.make_finder(index=index)
        get_url = self.patch(finder, "get_url")
        index = self.factory.make_url()
        get_url.return_value.json.return_value = {"dl": index}

        found_index = finder.get_index_dl()
        self.assertEqual(index, found_index)
        call = mock.call(f"{finder.index}config.json")
        self.assertIn(call, get_url.call_args_list)

    def test_get_index_dl_no_dl_key(self):
        finder = self.make_finder()
        get_url = self.patch(finder, "get_url")
        get_url.return_value.json.return_value = {}

        self.assertRaises(exceptions.DownloadError, finder.get_index_dl)

    def test_find(self):
        finder = self.make_finder()
        url = self.factory.make_url()
        self.patch(finder, "get_source_url").return_value = url

        disc_source = finder.find()
        self.assertIsInstance(disc_source, crate.CrateDiscoveredSource)
        self.assertEqual([url], disc_source.urls)

    def test_get_release_history(self):
        finder = self.make_finder(name=self.factory.make_string())
        data = {
            'versions': [
                {'num': '2.0.0', 'created_at': '2024-02-01T00:00:00.000Z'},
                {'num': '1.0.0', 'created_at': '2024-01-01T00:00:00.000Z'},
            ]
        }
        get = self.patch_get_with_response(requests.codes.ok, json=data)

        history = finder.get_release_history()
        self.assertEqual(
            ['1.0.0', '2.0.0'], [item['version'] for item in history]
        )
        get.assert_called_with(
            f"{crate.CRATES_API}{finder.name}/versions",
            headers={'User-Agent': 'soufi'},
            timeout=finder.timeout,
        )

    def test_get_release_history_raises_when_request_fails(self):
        finder = self.make_finder(name=self.factory.make_string())
        self.patch_get_with_response(requests.codes.bad_request)

        self.assertRaises(
            exceptions.SourceNotFound,
            finder.get_release_history,
        )

    def test_get_release_history_skips_yanked_and_empty_versions(self):
        finder = self.make_finder(name=self.factory.make_string())
        data = {
            'versions': [
                {'num': '3.0.0', 'yanked': True},
                {'created_at': '2024-03-01T00:00:00.000Z'},
                {'num': '2.0.0', 'created_at': '2024-02-01T00:00:00.000Z'},
            ]
        }
        self.patch_get_with_response(requests.codes.ok, json=data)

        history = finder.get_release_history()
        self.assertEqual(['2.0.0'], [item['version'] for item in history])

    def test_get_release_history_raises_when_all_versions_filtered(self):
        finder = self.make_finder(name=self.factory.make_string())
        data = {
            'versions': [
                {'num': '3.0.0', 'yanked': True},
                {'created_at': '2024-03-01T00:00:00.000Z'},
            ]
        }
        self.patch_get_with_response(requests.codes.ok, json=data)

        self.assertRaises(
            exceptions.SourceNotFound,
            finder.get_release_history,
        )


class TestCrateDiscoveredSource(base.TestCase):
    def make_discovered_source(self, url=None):
        if url is None:
            url = self.factory.make_url()
        return crate.CrateDiscoveredSource([url])

    def test_repr(self):
        url = self.factory.make_url()
        pds = self.make_discovered_source(url)
        self.assertEqual(url, repr(pds))

    def test_make_archive(self):
        cds = self.make_discovered_source()
        self.assertEqual(cds.make_archive, cds.remote_url_is_archive)
