import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import MagicMock, patch

import update_klookup_source as updater

LIVE_FIXTURE = '''# Copyright 2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

EAPI=8
inherit cmake git-r3
DESCRIPTION="KDE Plasma 6 lookup tool with search providers"
HOMEPAGE="https://github.com/arran4/klookup"
EGIT_REPO_URI="https://github.com/arran4/klookup.git"
LICENSE="GPL-3+ BSD-2 CC-BY-SA-4.0 CC0-1.0 MIT"
KEYWORDS=""
DEPEND="dev-qt/qtbase:6[dbus,gui,network,widgets]"
'''


class TestUpdater(unittest.TestCase):
    def setUp(self):
        self.tempdir = tempfile.TemporaryDirectory()
        self.original_cwd = os.getcwd()
        os.chdir(self.tempdir.name)
        updater.PACKAGE_DIR.mkdir(parents=True)
        updater.LIVE_EBUILD.write_text(LIVE_FIXTURE)

    def tearDown(self):
        os.chdir(self.original_cwd)
        self.tempdir.cleanup()

    @patch('update_klookup_source.urllib.request.urlopen')
    def test_no_releases(self, urlopen):
        response = MagicMock()
        response.read.return_value = b'[]'
        urlopen.return_value.__enter__.return_value = response
        self.assertEqual(updater.get_releases(), [])
        with patch.object(updater, 'get_releases', return_value=[]), \
             patch('update_klookup_source.subprocess.run') as run:
            updater.main()
        run.assert_not_called()

    def test_filters_drafts_prereleases_bad_tags_and_duplicates(self):
        releases = [
            {'tag_name': 'v1.0.0', 'draft': True},
            {'tag_name': 'v2.0.0', 'prerelease': True},
            {'tag_name': '../escape'},
            {'tag_name': 'v1.0.0'},
            {'tag_name': '1.0.0'},
            {'tag_name': 'v1.0.0-rc1'},
            {'tag_name': 'v1.2.0'},
        ]
        self.assertEqual(list(updater.eligible_releases(releases)),
                         [('v1.0.0', '1.0.0'), ('v1.2.0', '1.2.0')])

    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[{'tag_name': 'v1.0.0'}])
    def test_generates_source_ebuild_manifest_without_masters(self, get_releases, run):
        updater.main()
        generated = (updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').read_text()
        self.assertIn('inherit cmake\n', generated)
        self.assertNotIn('git-r3', generated)
        self.assertTrue(generated.startswith('# Copyright '))
        self.assertIn('SRC_URI="\n\thttps://github.com/arran4/klookup/archive/refs/tags/v1.0.0.tar.gz\n\t\t-> ${P}.tar.gz\n"', generated)
        self.assertIn('S="${WORKDIR}/klookup-${PV}"', generated)
        self.assertIn('KEYWORDS="~amd64"', generated)
        self.assertIn('LICENSE="GPL-3+ BSD-2 CC-BY-SA-4.0 CC0-1.0 MIT"', generated)
        self.assertIn('dev-qt/qtbase:6[dbus,gui,network,widgets]', generated)
        self.assertEqual(run.call_count, 1)
        self.assertEqual(run.call_args_list[0].args[0],
                         ['g2', 'manifest', 'upsert-from-url',
                          'https://github.com/arran4/klookup/archive/refs/tags/v1.0.0.tar.gz',
                          'klookup-1.0.0.tar.gz', 'app-misc/klookup/Manifest'])
        self.assertTrue(all(call.kwargs['check'] for call in run.call_args_list))
        self.assertFalse(any('cache' in call.args[0] for call in run.call_args_list))
        run.reset_mock()
        updater.main()
        run.assert_not_called()

    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[{'tag_name': '1.0.0'}, {'tag_name': 'v1.1.0'}])
    def test_existing_does_not_hide_newer_and_unprefixed_tags(self, get_releases, run):
        (updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').write_text('existing')
        updater.main()
        self.assertFalse((updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').exists())
        self.assertTrue((updater.PACKAGE_DIR / 'klookup-1.1.0.ebuild').is_file())
        self.assertIn('refs/tags/v1.1.0.tar.gz', (updater.PACKAGE_DIR / 'klookup-1.1.0.ebuild').read_text())
        self.assertEqual(run.call_count, 1)


    def test_rejects_missing_acorn_license(self):
        updater.LIVE_EBUILD.write_text(LIVE_FIXTURE.replace(' CC0-1.0 MIT', ' CC0-1.0'))
        with self.assertRaisesRegex(ValueError, 'declare MIT'):
            updater.release_ebuild('v1.0.0', '1.0.0')

    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[{'tag_name': 'v1.0.0'}])
    def test_revisioned_release_is_already_packaged(self, get_releases, run):
        revised = updater.PACKAGE_DIR / 'klookup-1.0.0-r1.ebuild'
        revised.write_text('patched')
        updater.main()
        self.assertEqual(revised.read_text(), 'patched')
        self.assertFalse((updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').exists())
        run.assert_not_called()

    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[{'tag_name': 'v1.1.0'}])
    def test_new_release_prunes_older_revisions_cache_and_distfile(self, get_releases, run):
        for name in ('klookup-1.0.0.ebuild', 'klookup-1.0.0-r1.ebuild'):
            (updater.PACKAGE_DIR / name).write_text('obsolete')
            cache = Path('metadata/md5-cache/app-misc') / name.removesuffix('.ebuild')
            cache.parent.mkdir(parents=True, exist_ok=True)
            cache.write_text('obsolete cache')
        manifest = updater.PACKAGE_DIR / 'Manifest'
        manifest.write_text('DIST klookup-1.0.0.tar.gz 10 BLAKE2B old SHA512 old\n'
                            'DIST other-source.tar.gz 10 BLAKE2B keep SHA512 keep\n')
        updater.main()
        for name in ('klookup-1.0.0.ebuild', 'klookup-1.0.0-r1.ebuild'):
            self.assertFalse((updater.PACKAGE_DIR / name).exists())
            self.assertFalse((Path('metadata/md5-cache/app-misc') /
                              name.removesuffix('.ebuild')).exists())
        self.assertTrue((updater.PACKAGE_DIR / 'klookup-1.1.0.ebuild').exists())
        self.assertNotIn('klookup-1.0.0.tar.gz', manifest.read_text())
        self.assertIn('other-source.tar.gz', manifest.read_text())
        self.assertEqual(run.call_count, 1)

    @patch('update_klookup_source.subprocess.run', side_effect=subprocess.CalledProcessError(1, 'g2'))
    @patch.object(updater, 'get_releases', return_value=[{'tag_name': 'v1.0.0'}])
    def test_manifest_failure_prevents_pr(self, get_releases, run):
        with self.assertRaises(subprocess.CalledProcessError):
            updater.main()
        self.assertEqual(run.call_count, 1)

    @patch('update_klookup_source.urllib.request.urlopen', side_effect=OSError('offline'))
    def test_api_failure_is_not_silent(self, urlopen):
        with self.assertRaises(OSError):
            updater.get_releases()


    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[
        {'tag_name': 'v1.9.0'}, {'tag_name': 'v1.2.0'},
        {'tag_name': 'v1.10.0'}])
    def test_only_newest_semver_release_is_packaged(self, get_releases, run):
        updater.main()
        self.assertTrue((updater.PACKAGE_DIR / 'klookup-1.10.0.ebuild').exists())
        self.assertFalse((updater.PACKAGE_DIR / 'klookup-1.9.0.ebuild').exists())
        self.assertFalse((updater.PACKAGE_DIR / 'klookup-1.2.0.ebuild').exists())
        self.assertEqual(run.call_count, 1)

    @patch('update_klookup_source.subprocess.run')
    @patch.object(updater, 'get_releases', return_value=[
        {'tag_name': 'v1.1.0'}])
    def test_removes_obsolete_release_ebuild_cache_and_manifest(self, get_releases, run):
        (updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').write_text('obsolete')
        cache = Path('metadata/md5-cache/app-misc/klookup-1.0.0')
        cache.parent.mkdir(parents=True)
        cache.write_text('obsolete cache')
        manifest = updater.PACKAGE_DIR / 'Manifest'
        manifest.write_text('DIST klookup-1.0.0.tar.gz 10 BLAKE2B old SHA512 old\n'
                            'DIST other-source.tar.gz 10 BLAKE2B keep SHA512 keep\n')
        updater.main()
        self.assertFalse((updater.PACKAGE_DIR / 'klookup-1.0.0.ebuild').exists())
        self.assertFalse(cache.exists())
        self.assertNotIn('klookup-1.0.0', manifest.read_text())
        self.assertIn('other-source.tar.gz', manifest.read_text())
        self.assertEqual(run.call_count, 1)


if __name__ == '__main__':
    unittest.main()
