# Copyright (C) 2018  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information

import unittest
from unittest.mock import patch

from swh.model.from_disk import DentryPerms
from swh.model.hashutil import MultiHash, hash_to_bytes
from swh.model.model import Directory
from swh.storage.algos.dir_iterators import dir_iterator

# flake8: noqa


class DirectoryModel(object):
    """
    Quick and dirty directory model to ease the writing
    of directory iterators and revision trees differential tests.
    """

    def __init__(self, name=""):
        self.data = {}
        self.data["name"] = name
        self.data["perms"] = DentryPerms.directory
        self.data["type"] = "dir"
        self.data["entries"] = []
        self.data["entry_idx"] = {}

    def __getitem__(self, item):
        if item == "target":
            return Directory.from_dict(
                {
                    "entries": [
                        {
                            "name": entry["name"],
                            "target": entry["target"],
                            "type": entry["type"],
                            "perms": entry["perms"],
                        }
                        for entry in self.data["entries"]
                    ]
                }
            ).id
        else:
            return self.data[item]

    def add_file(self, path, sha1=None):
        path_parts = path.split(b"/")
        sha1 = (
            hash_to_bytes(sha1) if sha1 else MultiHash.from_data(path).digest()["sha1"]
        )
        if len(path_parts) == 1:
            self["entry_idx"][path] = len(self["entries"])
            self["entries"].append(
                {
                    "target": sha1,
                    "name": path,
                    "perms": DentryPerms.content,
                    "type": "file",
                }
            )
        else:
            if not path_parts[0] in self["entry_idx"]:
                self["entry_idx"][path_parts[0]] = len(self["entries"])
                self["entries"].append(DirectoryModel(path_parts[0]))
            if path_parts[1]:
                dir_idx = self["entry_idx"][path_parts[0]]
                self["entries"][dir_idx].add_file(b"/".join(path_parts[1:]), sha1)

    def get_hash_data(self, entry_hash):
        if self["target"] == entry_hash:
            ret = []
            for e in self["entries"]:
                ret.append(
                    {
                        "target": e["target"],
                        "name": e["name"],
                        "perms": e["perms"],
                        "type": e["type"],
                    }
                )
            return ret
        else:
            for e in self["entries"]:
                if e["type"] == "file" and e["target"] == entry_hash:
                    return e
                elif e["type"] == "dir":
                    data = e.get_hash_data(entry_hash)
                    if data:
                        return data
            return None

    def get_path_data(self, path):
        path_parts = path.split(b"/")
        entry_idx = self["entry_idx"][path_parts[0]]
        entry = self["entries"][entry_idx]
        if len(path_parts) == 1:
            return {
                "target": entry["target"],
                "name": entry["name"],
                "perms": entry["perms"],
                "type": entry["type"],
            }
        else:
            return entry.get_path_data(b"/".join(path_parts[1:]))


@patch("swh.storage.algos.dir_iterators._get_dir")
class TestDirectoryIterator(unittest.TestCase):
    def check_iterated_paths(self, dir_model, expected_paths_order, mock_get_dir):
        def _get_dir(*args, **kwargs):
            return dir_model.get_hash_data(args[1])

        mock_get_dir.side_effect = _get_dir  # noqa
        paths_order = [e["path"] for e in dir_iterator(None, dir_model["target"])]
        self.assertEqual(paths_order, expected_paths_order)

    def test_dir_iterator_empty_dir(self, mock_get_dir):
        dir_model = DirectoryModel()
        expected_paths_order = []
        self.check_iterated_paths(dir_model, expected_paths_order, mock_get_dir)

    def test_dir_iterator_no_empty_dirs(self, mock_get_dir):
        dir_model = DirectoryModel()
        dir_model.add_file(b"xyz/gtr/uhb")
        dir_model.add_file(b"bca/ef")
        dir_model.add_file(b"abc/ab")
        dir_model.add_file(b"abc/bc")
        dir_model.add_file(b"xyz/ouy/poi")

        expected_paths_order = [
            b"abc",
            b"abc/ab",
            b"abc/bc",
            b"bca",
            b"bca/ef",
            b"xyz",
            b"xyz/gtr",
            b"xyz/gtr/uhb",
            b"xyz/ouy",
            b"xyz/ouy/poi",
        ]

        self.check_iterated_paths(dir_model, expected_paths_order, mock_get_dir)

    def test_dir_iterator_with_empty_dirs(self, mock_get_dir):
        dir_model = DirectoryModel()
        dir_model.add_file(b"xyz/gtr/")
        dir_model.add_file(b"bca/ef")
        dir_model.add_file(b"abc/")
        dir_model.add_file(b"xyz/ouy/poi")

        expected_paths_order = [
            b"abc",
            b"bca",
            b"bca/ef",
            b"xyz",
            b"xyz/gtr",
            b"xyz/ouy",
            b"xyz/ouy/poi",
        ]

        self.check_iterated_paths(dir_model, expected_paths_order, mock_get_dir)
