import os
import shutil
import unittest
from unittest.mock import patch

import piuparts
from piuparts import is_broken_symlink


class DefaultsFactoryTests(unittest.TestCase):
    def setUp(self):
        self.df = piuparts.DefaultsFactory()
        piuparts.settings = piuparts.Settings()

    def test_new_defaults_return_debian_defaults(self):
        # mock the guess_flavor function as it runs lsb_release in a subprocess
        with patch.object(self.df, "guess_flavor", return_value="debian") as guess_flavor_mock:
            defaults = self.df.new_defaults()
            guess_flavor_mock.assert_called_once()

        self.assertEqual(defaults.get_keyring(), "/usr/share/keyrings/debian-archive-keyring.gpg")
        self.assertEqual(
            defaults.get_components(),
            ["main", "contrib", "non-free", "non-free-firmware"],
        )
        self.assertEqual(
            defaults.get_mirror(),
            [
                (
                    "http://deb.debian.org/debian",
                    ["main", "contrib", "non-free", "non-free-firmware"],
                )
            ],
        )
        self.assertEqual(defaults.get_distribution(), ["sid"])

    def test_new_defaults_return_ubuntu_defaults(self):
        with patch.object(self.df, "guess_flavor", return_value="ubuntu") as guess_flavor_mock:
            defaults = self.df.new_defaults()
            guess_flavor_mock.assert_called_once()

        self.assertEqual(defaults.get_keyring(), "/usr/share/keyrings/ubuntu-archive-keyring.gpg")
        self.assertEqual(defaults.get_components(), ["main", "universe", "restricted", "multiverse"])
        self.assertEqual(
            defaults.get_mirror(),
            [
                (
                    "http://archive.ubuntu.com/ubuntu",
                    ["main", "universe", "restricted", "multiverse"],
                )
            ],
        )

    def test_new_defaults_panics_with_unknown_flavor(self):
        with (
            patch.object(self.df, "guess_flavor", return_value="centos") as guess_flavor_mock,
            patch.object(piuparts, "panic", side_effect=SystemExit) as panic_mock,
        ):
            with self.assertRaises(SystemExit):
                self.df.new_defaults()

            guess_flavor_mock.assert_called_once()
            panic_mock.assert_called_once()


class IsBrokenSymlinkTests(unittest.TestCase):
    testdir = "is-broken-symlink-testdir"

    def symlink(self, target, name):
        pathname = os.path.join(self.testdir, name)
        os.symlink(target, pathname)
        self.symlinks.append(pathname)

    def setUp(self):
        self.symlinks = []
        os.mkdir(self.testdir)
        self.symlink("notexist", "relative-broken")
        self.symlink("relative-broken", "relative-broken-to-symlink")
        self.symlink(".", "relative-works")
        self.symlink("relative-works", "relative-works-to-symlink")
        self.symlink("/etc", "absolute-broken")
        self.symlink("absolute-broken", "absolute-broken-to-symlink")
        self.symlink("/", "absolute-works")
        self.symlink("/absolute-works", "absolute-works-to-symlink")
        os.mkdir(os.path.join(self.testdir, "dir"))
        self.symlink("dir", "dir-link")
        os.mkdir(os.path.join(self.testdir, "dir/subdir"))
        self.symlink("subdir", "dir/subdir-link")
        self.symlink("notexist/", "trailing-slash-broken")
        self.symlink("dir/", "trailing-slash-works")
        self.symlink("selfloop", "selfloop")
        self.symlink("/absolute-selfloop", "absolute-selfloop")
        self.symlink("../dir/selfloop", "dir/selfloop")
        self.symlink("../dir-link/selfloop", "dir/selfloop1")
        self.symlink("../../dir/subdir/selfloop", "dir/subdir/selfloop")
        self.symlink("../../dir-link/subdir/selfloop", "dir/subdir/selfloop1")
        self.symlink("../../link/subdir-link/selfloop", "dir/subdir/selfloop2")
        self.symlink("../../dir-link/subdir-link/selfloop", "dir/subdir/selfloop3")
        self.symlink("explode/bomb", "explode")

    def tearDown(self):
        shutil.rmtree(self.testdir)

    def testRelativeBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "relative-broken"))

    def testRelativeBrokenToSymlink(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "relative-broken-to-symlink"))

    def testAbsoluteBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "absolute-broken"))

    def testAbsoluteBrokenToSymlink(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "absolute-broken-to-symlink"))

    def testTrailingSlashBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "trailing-slash-broken"))

    def testSelfLoopBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "selfloop"))

    def testExpandingSelfLoopBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "explode"))

    def testAbsoluteSelfLoopBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "absolute-selfloop"))

    def testSubdirSelfLoopBroken(self):
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/selfloop"))
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/selfloop1"))
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/subdir/selfloop"))
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/subdir/selfloop1"))
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/subdir/selfloop2"))
        self.assertTrue(is_broken_symlink(self.testdir, self.testdir, "dir/subdir/selfloop3"))

    def testRelativeWorks(self):
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "relative-works"))

    def testRelativeWorksToSymlink(self):
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "relative-works-to-symlink"))

    def testAbsoluteWorks(self):
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "absolute-works"))

    def testAbsoluteWorksToSymlink(self):
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "absolute-works-to-symlink"))

    def testTrailingSlashWorks(self):
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "trailing-slash-works"))

    def testMultiLevelNestedSymlinks(self):
        # target/first-link -> ../target/second-link -> ../target

        os.mkdir(os.path.join(self.testdir, "target"))
        self.symlink("../target", "target/second-link")
        self.symlink("../target/second-link", "target/first-link")
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "target/first-link"))

    def testMultiLevelNestedAbsoluteSymlinks(self):
        # first-link -> /second-link/final-target
        # second-link -> /target-dir

        os.mkdir(os.path.join(self.testdir, "final-dir"))
        os.mkdir(os.path.join(self.testdir, "final-dir/final-target"))
        self.symlink("/second-link/final-target", "first-link")
        self.symlink("/final-dir", "second-link")
        self.assertFalse(is_broken_symlink(self.testdir, self.testdir, "first-link"))
