using System.Collections.Generic;
using SabreTools.Hashing;
using SabreTools.Metadata.DatItems;
using SabreTools.Metadata.DatItems.Formats;
using Xunit;

namespace SabreTools.Metadata.DatFiles.Test
{
    public class ItemDatabaseTests
    {
        #region AddItem

        [Fact]
        public void AddItem_Disk_WithHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem disk = new Disk
            {
                Name = "item",
                SHA1 = "deadbeef",
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            disk.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            disk.MachineIndex = machineIndex;

            _ = dict.AddItem(disk, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Disk? actualDisk = actual as Disk;
            Assert.NotNull(actualDisk);
            Assert.Equal(Data.Models.Metadata.ItemStatus.None, actualDisk.Status);
        }

        [Fact]
        public void AddItem_Disk_WithoutHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem disk = new Disk { Name = "item" };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            disk.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            disk.MachineIndex = machineIndex;

            _ = dict.AddItem(disk, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Disk? actualDisk = actual as Disk;
            Assert.NotNull(actualDisk);
            Assert.Equal(Data.Models.Metadata.ItemStatus.Nodump, actualDisk.Status);
        }

        [Fact]
        public void AddItem_File_WithHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            var file = new File { SHA1 = "deadbeef" };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            file.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            file.MachineIndex = machineIndex;

            _ = dict.AddItem(file, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Assert.True(actual is File);
            //Assert.Equal("none", actual.GetStringFieldValue(File.StatusKey));
        }

        [Fact]
        public void AddItem_File_WithoutHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem file = new File();

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            file.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            file.MachineIndex = machineIndex;

            _ = dict.AddItem(file, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Assert.True(actual is File);
            //Assert.Equal("nodump", actual.GetStringFieldValue(File.StatusKey));
        }

        [Fact]
        public void AddItem_Media_WithHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem media = new Media
            {
                SHA1 = "deadbeef",
                Name = "item"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            media.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            media.MachineIndex = machineIndex;

            _ = dict.AddItem(media, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Assert.True(actual is Media);
            //Assert.Equal("none", actual.GetStringFieldValue(Data.Models.Metadata.Media.StatusKey));
        }

        [Fact]
        public void AddItem_Media_WithoutHashes()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem media = new Media
            {
                Name = "item"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            media.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            media.MachineIndex = machineIndex;

            _ = dict.AddItem(media, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Assert.True(actual is Media);
            //Assert.Equal("nodump", actual.GetStringFieldValue(Data.Models.Metadata.Media.StatusKey));
        }

        [Fact]
        public void AddItem_Rom_WithHashesWithSize()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem rom = new Rom
            {
                Name = "item",
                Size = 12345,
                SHA1 = "deadbeef"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            rom.MachineIndex = machineIndex;

            _ = dict.AddItem(rom, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Rom? actualRom = actual as Rom;
            Assert.NotNull(actualRom);
            Assert.Equal(12345, actualRom.Size);
            Assert.Equal("deadbeef", actualRom.SHA1);
            Assert.Equal(Data.Models.Metadata.ItemStatus.None, actualRom.Status);
        }

        [Fact]
        public void AddItem_Rom_WithoutHashesWithSize()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem rom = new Rom()
            {
                Name = "item",
                Size = 12345,
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            rom.MachineIndex = machineIndex;

            _ = dict.AddItem(rom, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Rom? actualRom = actual as Rom;
            Assert.NotNull(actualRom);
            Assert.Equal(12345, actualRom.Size);
            Assert.Null(actualRom.SHA1);
            Assert.Equal(Data.Models.Metadata.ItemStatus.Nodump, actualRom.Status);
        }

        [Fact]
        public void AddItem_Rom_WithHashesWithoutSize()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem rom = new Rom
            {
                Name = "item",
                SHA1 = "deadbeef"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            rom.MachineIndex = machineIndex;

            _ = dict.AddItem(rom, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Rom? actualRom = actual as Rom;
            Assert.NotNull(actualRom);
            Assert.Null(actualRom.Size);
            Assert.Equal("deadbeef", actualRom.SHA1);
            Assert.Equal(Data.Models.Metadata.ItemStatus.None, actualRom.Status);
        }

        [Fact]
        public void AddItem_Rom_WithoutHashesWithoutSize()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem rom = new Rom { Name = "item" };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            rom.MachineIndex = machineIndex;

            _ = dict.AddItem(rom, statsOnly: false);

            DatItem actual = Assert.Single(dict.GetItemsForBucket("default")).Value;
            Rom? actualRom = actual as Rom;
            Assert.NotNull(actualRom);
            Assert.Equal(0, actualRom.Size);
            Assert.Equal(HashType.SHA1.ZeroString, actualRom.SHA1);
            Assert.Equal(Data.Models.Metadata.ItemStatus.None, actualRom.Status);
        }

        [Fact]
        public void AddItem_StatsOnly()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem item = new Rom { Name = "item" };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: true);

            Assert.Empty(dict.GetItemsForBucket("default"));
        }

        [Fact]
        public void AddItem_NormalAdd()
        {
            Source source = new Source(0, source: null);
            Machine machine = new Machine();

            DatItem item = new Rom { Name = "item" };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            Assert.Single(dict.GetItemsForBucket("default"));
        }

        #endregion

        #region AddMachine

        [Fact]
        public void AddMachineTest()
        {
            Machine machine = new Machine();
            var dict = new ItemDatabase();
            long machineIndex = dict.AddMachine(machine);

            Assert.Equal(0, machineIndex);
            Assert.Single(dict.GetMachines());
        }

        #endregion

        #region AddSource

        [Fact]
        public void AddSourceTest()
        {
            Source source = new Source(0, source: null);
            var dict = new ItemDatabase();
            long sourceIndex = dict.AddSource(source);

            Assert.Equal(0, sourceIndex);
            Assert.NotNull(dict.GetSource(0).Value);
        }

        #endregion

        #region ClearMarked

        [Fact]
        public void ClearMarkedTest()
        {
            // Setup the items
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem rom1 = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            DatItem rom2 = new Rom
            {
                Name = "rom-2",
                Size = 1024,
                CRC32 = "DEADBEEF",
                RemoveFlag = true,
                SHA1 = "000000e948edcb4f7704b8af85a77a3339ecce44"
            };

            // Setup the dictionary
            var dict = new ItemDatabase();
            long sourceIndex = dict.AddSource(source);
            rom1.SourceIndex = sourceIndex;
            rom2.SourceIndex = sourceIndex;

            long machineIndex = dict.AddMachine(machine);
            rom1.MachineIndex = machineIndex;
            rom2.MachineIndex = machineIndex;

            dict.AddItem(rom1, statsOnly: false);
            dict.AddItem(rom2, statsOnly: false);

            dict.ClearMarked();
            string key = Assert.Single(dict.SortedKeys);
            Assert.Equal("game-1", key);
            Dictionary<long, DatItem> items = dict.GetItemsForBucket(key);
            Assert.Single(items);
        }

        #endregion

        #region GetItemsForBucket

        [Fact]
        public void GetItemsForBucket_NullBucketName()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            DatItem item = new Rom();

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            var actual = dict.GetItemsForBucket(null, filter: false);

            Assert.Empty(actual);
        }

        [Fact]
        public void GetItemsForBucket_InvalidBucketName()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            DatItem item = new Rom();

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            var actual = dict.GetItemsForBucket("INVALID", filter: false);

            Assert.Empty(actual);
        }

        [Fact]
        public void GetItemsForBucket_RemovedFilter()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            DatItem item = new Rom { RemoveFlag = true };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            var actual = dict.GetItemsForBucket("machine", filter: true);

            Assert.Empty(actual);
        }

        [Fact]
        public void GetItemsForBucket_RemovedNoFilter()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            DatItem item = new Rom { RemoveFlag = true };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            var actual = dict.GetItemsForBucket("machine", filter: false);

            Assert.Single(actual);
        }

        [Fact]
        public void GetItemsForBucket_Standard()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            DatItem item = new Rom();

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            var actual = dict.GetItemsForBucket("machine", filter: false);

            Assert.Single(actual);
        }

        #endregion

        #region GetMachine

        [Fact]
        public void GetMachineTest()
        {
            Machine machine = new Machine();
            var dict = new ItemDatabase();
            long machineIndex = dict.AddMachine(machine);

            Assert.Equal(0, machineIndex);
            var actual = dict.GetMachine(machineIndex);
            Assert.NotNull(actual.Value);
        }

        #endregion

        #region GetSource

        [Fact]
        public void GetSourceTest()
        {
            Source source = new Source(0, source: null);
            var dict = new ItemDatabase();
            long sourceIndex = dict.AddSource(source);

            Assert.Equal(0, sourceIndex);
            var actual = dict.GetSource(sourceIndex);
            Assert.NotNull(actual.Value);
        }

        #endregion

        #region RemoveBucket

        [Fact]
        public void RemoveBucketTest()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem item = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            dict.AddItem(item, statsOnly: false);

            dict.RemoveBucket("game-1");

            Assert.Empty(dict.GetItemsForBucket("game-1"));
        }

        #endregion

        #region RemoveItem

        [Fact]
        public void RemoveItemTest()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem item = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            long itemIndex = dict.AddItem(item, statsOnly: false);

            dict.RemoveItem(itemIndex);

            Assert.Empty(dict.GetItemsForBucket("game-1"));
        }

        #endregion

        #region RemoveMachine

        [Fact]
        public void RemoveMachineTest()
        {
            Machine machine = new Machine();
            var dict = new ItemDatabase();
            long machineIndex = dict.AddMachine(machine);

            bool actual = dict.RemoveMachine(machineIndex);
            Assert.True(actual);
            Assert.Empty(dict.GetMachines());
        }

        #endregion

        #region BucketBy

        [Theory]
        [InlineData(ItemKey.NULL, 2)]
        [InlineData(ItemKey.Machine, 2)]
        [InlineData(ItemKey.CRC32, 1)]
        [InlineData(ItemKey.SHA1, 4)]
        public void BucketByTest(ItemKey itemKey, int expected)
        {
            // Setup the items
            Source source = new Source(0, source: null);

            Machine machine1 = new Machine { Name = "game-1" };

            Machine machine2 = new Machine { Name = "game-2" };

            DatItem rom1 = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            DatItem rom2 = new Rom
            {
                Name = "rom-2",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "000000e948edcb4f7704b8af85a77a3339ecce44"
            };

            DatItem rom3 = new Rom
            {
                Name = "rom-3",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "00000ea4014ce66679e7e17d56ac510f67e39e26"
            };

            DatItem rom4 = new Rom
            {
                Name = "rom-4",
                Size = 1024,
                CRC32 = "DEADBEEF",
                SHA1 = "00000151d437442e74e5134023fab8bf694a2487"
            };

            // Setup the dictionary
            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom1.SourceIndex = sourceIndex;
            rom2.SourceIndex = sourceIndex;
            rom3.SourceIndex = sourceIndex;
            rom4.SourceIndex = sourceIndex;

            long machine1Index = dict.AddMachine(machine1);
            rom1.MachineIndex = machine1Index;
            rom2.MachineIndex = machine1Index;

            long machine2Index = dict.AddMachine(machine2);
            rom3.MachineIndex = machine2Index;
            rom4.MachineIndex = machine2Index;

            dict.AddItem(rom1, statsOnly: false);
            dict.AddItem(rom2, statsOnly: false);
            dict.AddItem(rom3, statsOnly: false);
            dict.AddItem(rom4, statsOnly: false);

            dict.BucketBy(itemKey);
            Assert.Equal(expected, dict.SortedKeys.Length);
        }

        #endregion

        #region Deduplicate

        [Fact]
        public void DeduplicateTest()
        {
            // Setup the items
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem rom1 = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            DatItem rom2 = new Rom
            {
                Name = "rom-2",
                Size = 1024,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            // Setup the dictionary
            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom1.SourceIndex = sourceIndex;
            rom2.SourceIndex = sourceIndex;

            long machineIndex = dict.AddMachine(machine);
            rom1.MachineIndex = machineIndex;
            rom2.MachineIndex = machineIndex;

            dict.AddItem(rom1, statsOnly: false);
            dict.AddItem(rom2, statsOnly: false);

            dict.Deduplicate();
            Assert.Equal(1, dict.DatStatistics.TotalCount);
        }

        #endregion

        #region GetDuplicateStatus

        [Fact]
        public void GetDuplicateStatus_NullOther_NoDupe()
        {
            var dict = new ItemDatabase();

            Source? selfSource = null;
            Source? lastSource = null;

            KeyValuePair<long, DatItem>? item = new KeyValuePair<long, DatItem>(0, new Rom());
            KeyValuePair<long, DatItem>? lastItem = null;

            var actual = dict.GetDuplicateStatus(item, selfSource, lastItem, lastSource);
            Assert.Equal((DupeType)0x00, actual);
        }

        [Fact]
        public void GetDuplicateStatus_DifferentTypes_NoDupe()
        {
            var dict = new ItemDatabase();

            Source? selfSource = null;
            Source? lastSource = null;

            KeyValuePair<long, DatItem>? rom = new KeyValuePair<long, DatItem>(0, new Rom());
            KeyValuePair<long, DatItem>? lastItem = new KeyValuePair<long, DatItem>(1, new Disk());
            var actual = dict.GetDuplicateStatus(rom, selfSource, lastItem, lastSource);
            Assert.Equal((DupeType)0x00, actual);
        }

        [Fact]
        public void GetDuplicateStatus_MismatchedHashes_NoDupe()
        {
            var dict = new ItemDatabase();

            Source? sourceA = new Source(0);
            long sourceAIndex = dict.AddSource(sourceA);
            Source? sourceB = new Source(1);
            long sourceBIndex = dict.AddSource(sourceB);

            Machine? machineA = new Machine { Name = "name-same" };
            long machineAIndex = dict.AddMachine(machineA);

            Machine? machineB = new Machine { Name = "name-same" };
            long machineBIndex = dict.AddMachine(machineB);

            var romA = new Rom
            {
                SourceIndex = sourceAIndex,
                MachineIndex = machineAIndex,
                Name = "same-name",
                CRC32 = "BEEFDEAD"
            };
            long romAIndex = dict.AddItemInternal(romA);
            KeyValuePair<long, DatItem>? romAPair = new KeyValuePair<long, DatItem>(romAIndex, romA);

            var romB = new Rom
            {
                SourceIndex = sourceBIndex,
                MachineIndex = machineBIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romBIndex = dict.AddItemInternal(romB);
            KeyValuePair<long, DatItem>? romBPair = new KeyValuePair<long, DatItem>(romBIndex, romB);

            var actual = dict.GetDuplicateStatus(romAPair, sourceA, romBPair, sourceB);
            Assert.Equal((DupeType)0x00, actual);
        }

        [Fact]
        public void GetDuplicateStatus_DifferentSource_NameMatch_ExternalAll()
        {
            var dict = new ItemDatabase();

            Source? sourceA = new Source(0);
            long sourceAIndex = dict.AddSource(sourceA);
            Source? sourceB = new Source(1);
            long sourceBIndex = dict.AddSource(sourceB);

            Machine? machineA = new Machine { Name = "name-same" };
            long machineAIndex = dict.AddMachine(machineA);

            Machine? machineB = new Machine { Name = "name-same" };
            long machineBIndex = dict.AddMachine(machineB);

            var romA = new Rom
            {
                SourceIndex = sourceAIndex,
                MachineIndex = machineAIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romAIndex = dict.AddItemInternal(romA);
            KeyValuePair<long, DatItem>? romAPair = new KeyValuePair<long, DatItem>(romAIndex, romA);

            var romB = new Rom
            {
                SourceIndex = sourceBIndex,
                MachineIndex = machineBIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romBIndex = dict.AddItemInternal(romB);
            KeyValuePair<long, DatItem>? romBPair = new KeyValuePair<long, DatItem>(romBIndex, romB);

            var actual = dict.GetDuplicateStatus(romAPair, sourceA, romBPair, sourceB);
            Assert.Equal(DupeType.External | DupeType.All, actual);
        }

        [Fact]
        public void GetDuplicateStatus_DifferentSource_NoNameMatch_ExternalHash()
        {
            var dict = new ItemDatabase();

            Source? sourceA = new Source(0);
            long sourceAIndex = dict.AddSource(sourceA);
            Source? sourceB = new Source(1);
            long sourceBIndex = dict.AddSource(sourceB);

            Machine? machineA = new Machine { Name = "name-same" };
            long machineAIndex = dict.AddMachine(machineA);

            Machine? machineB = new Machine { Name = "not-name-same" };
            long machineBIndex = dict.AddMachine(machineB);

            var romA = new Rom
            {
                SourceIndex = sourceAIndex,
                MachineIndex = machineAIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romAIndex = dict.AddItemInternal(romA);
            KeyValuePair<long, DatItem>? romAPair = new KeyValuePair<long, DatItem>(romAIndex, romA);

            var romB = new Rom
            {
                SourceIndex = sourceBIndex,
                MachineIndex = machineBIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romBIndex = dict.AddItemInternal(romB);
            KeyValuePair<long, DatItem>? romBPair = new KeyValuePair<long, DatItem>(romBIndex, romB);

            var actual = dict.GetDuplicateStatus(romAPair, sourceA, romBPair, sourceB);
            Assert.Equal(DupeType.External | DupeType.Hash, actual);
        }

        [Fact]
        public void GetDuplicateStatus_SameSource_NameMatch_InternalAll()
        {
            var dict = new ItemDatabase();

            Source? sourceA = new Source(0);
            long sourceAIndex = dict.AddSource(sourceA);
            Source? sourceB = new Source(0);
            long sourceBIndex = dict.AddSource(sourceB);

            Machine? machineA = new Machine { Name = "name-same" };
            long machineAIndex = dict.AddMachine(machineA);

            Machine? machineB = new Machine { Name = "name-same" };
            long machineBIndex = dict.AddMachine(machineB);

            var romA = new Rom
            {
                SourceIndex = sourceAIndex,
                MachineIndex = machineAIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romAIndex = dict.AddItemInternal(romA);
            KeyValuePair<long, DatItem>? romAPair = new KeyValuePair<long, DatItem>(romAIndex, romA);

            var romB = new Rom
            {
                SourceIndex = sourceBIndex,
                MachineIndex = machineBIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romBIndex = dict.AddItemInternal(romB);
            KeyValuePair<long, DatItem>? romBPair = new KeyValuePair<long, DatItem>(romBIndex, romB);

            var actual = dict.GetDuplicateStatus(romAPair, sourceA, romBPair, sourceB);
            Assert.Equal(DupeType.Internal | DupeType.All, actual);
        }

        [Fact]
        public void GetDuplicateStatus_SameSource_NoNameMatch_InternalHash()
        {
            var dict = new ItemDatabase();

            Source? sourceA = new Source(0);
            long sourceAIndex = dict.AddSource(sourceA);
            Source? sourceB = new Source(0);
            long sourceBIndex = dict.AddSource(sourceB);

            Machine? machineA = new Machine { Name = "name-same" };
            long machineAIndex = dict.AddMachine(machineA);

            Machine? machineB = new Machine { Name = "not-name-same" };
            long machineBIndex = dict.AddMachine(machineB);

            var romA = new Rom
            {
                SourceIndex = sourceAIndex,
                MachineIndex = machineAIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romAIndex = dict.AddItemInternal(romA);
            KeyValuePair<long, DatItem>? romAPair = new KeyValuePair<long, DatItem>(romAIndex, romA);

            var romB = new Rom
            {
                SourceIndex = sourceBIndex,
                MachineIndex = machineBIndex,
                Name = "same-name",
                CRC32 = "DEADBEEF"
            };
            long romBIndex = dict.AddItemInternal(romB);
            KeyValuePair<long, DatItem>? romBPair = new KeyValuePair<long, DatItem>(romBIndex, romB);

            var actual = dict.GetDuplicateStatus(romAPair, sourceA, romBPair, sourceB);
            Assert.Equal(DupeType.Internal | DupeType.Hash, actual);
        }

        #endregion

        #region GetDuplicates

        [Theory]
        [InlineData(true, 1)]
        [InlineData(false, 0)]
        public void GetDuplicatesTest(bool hasDuplicate, int expected)
        {
            // Setup the items
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem rom1 = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            DatItem rom2 = new Rom
            {
                Name = "rom-2",
                Size = 1024,
                SHA1 = "000000e948edcb4f7704b8af85a77a3339ecce44"
            };

            // Setup the dictionary
            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom1.SourceIndex = sourceIndex;
            rom2.SourceIndex = sourceIndex;

            long machineIndex = dict.AddMachine(machine);
            rom1.MachineIndex = machineIndex;
            rom2.MachineIndex = machineIndex;

            dict.AddItem(rom1, statsOnly: false);
            dict.AddItem(rom2, statsOnly: false);

            // Setup the test item
            DatItem rom = new Rom
            {
                Name = "rom-1",
                Size = hasDuplicate ? 1024 : 2048,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            var actual = dict.GetDuplicates(new KeyValuePair<long, DatItem>(-1, rom));
            Assert.Equal(expected, actual.Count);
        }

        #endregion

        #region HasDuplicates

        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public void HasDuplicatesTest(bool expected)
        {
            // Setup the items
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "game-1" };

            DatItem rom1 = new Rom
            {
                Name = "rom-1",
                Size = 1024,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            DatItem rom2 = new Rom
            {
                Name = "rom-2",
                Size = 1024,
                SHA1 = "000000e948edcb4f7704b8af85a77a3339ecce44"
            };

            // Setup the dictionary
            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            rom1.SourceIndex = sourceIndex;
            rom2.SourceIndex = sourceIndex;

            long machineIndex = dict.AddMachine(machine);
            rom1.MachineIndex = machineIndex;
            rom2.MachineIndex = machineIndex;

            dict.AddItem(rom1, statsOnly: false);
            dict.AddItem(rom2, statsOnly: false);

            // Setup the test item
            DatItem rom = new Rom
            {
                Name = "rom-1",
                Size = expected ? 1024 : 2048,
                SHA1 = "0000000fbbb37f8488100b1b4697012de631a5e6"
            };

            bool actual = dict.HasDuplicates(new KeyValuePair<long, DatItem>(-1, rom));
            Assert.Equal(expected, actual);
        }

        #endregion

        #region RecalculateStats

        [Fact]
        public void RecalculateStatsTest()
        {
            Source source = new Source(0, source: null);

            Machine machine = new Machine { Name = "machine" };

            var item = new Rom
            {
                Name = "rom",
                Size = 12345,
                CRC32 = "deadbeef"
            };

            var dict = new ItemDatabase();

            long sourceIndex = dict.AddSource(source);
            item.SourceIndex = sourceIndex;
            long machineIndex = dict.AddMachine(machine);
            item.MachineIndex = machineIndex;

            _ = dict.AddItem(item, statsOnly: false);

            Assert.Equal(1, dict.DatStatistics.TotalCount);
            Assert.Equal(1, dict.DatStatistics.GetItemCount(Data.Models.Metadata.ItemType.Rom));
            Assert.Equal(12345, dict.DatStatistics.TotalSize);
            Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.CRC32));
            Assert.Equal(0, dict.DatStatistics.GetHashCount(HashType.MD5));

            item.MD5 = "deadbeef";

            dict.RecalculateStats();

            Assert.Equal(1, dict.DatStatistics.TotalCount);
            Assert.Equal(1, dict.DatStatistics.GetItemCount(Data.Models.Metadata.ItemType.Rom));
            Assert.Equal(12345, dict.DatStatistics.TotalSize);
            Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.CRC32));
            Assert.Equal(1, dict.DatStatistics.GetHashCount(HashType.MD5));
        }

        #endregion
    }
}
