using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Utility;
using Duplicati.Proprietary.DiskImage.Disk;
using Duplicati.Proprietary.DiskImage.Filesystem.Fat32;
using Duplicati.Proprietary.DiskImage.General;

namespace Duplicati.Proprietary.DiskImage.Partition;

/// <summary>
/// Represents a GPT (GUID Partition Table) partition table.
/// GPT is the modern partition table format used on UEFI-based systems.
/// </summary>
internal class GPT : IPartitionTable
{
    // Constants for GPT parsing (from PartitionConstants)
    private const int HeaderSize = PartitionConstants.GptHeaderSize;
    private const long GptSignature = PartitionConstants.GptSignature;
    private const int MbrSize = PartitionConstants.MbrSize;
    private const ushort MbrBootSignature = PartitionConstants.MbrBootSignature;
    private const byte ProtectiveMbrType = PartitionConstants.ProtectiveMbrType;

    // Internal state
    private bool m_parsed = false;
    private bool m_disposed = false;

    // GPT Header fields
    private long m_signature;
    private uint m_revision;
    private uint m_headerSize;
    private uint m_headerCrc32;
    private long m_currentLba;
    private long m_backupLba;
    private long m_firstUsableLba;
    private long m_lastUsableLba;
    private Guid m_diskGuid;
    private long m_partitionEntryLba;
    private uint m_numPartitionEntries;
    private uint m_partitionEntrySize;
    private uint m_partitionEntryCrc32;

    // Additional tracking
    private IRawDisk? m_rawDisk;
    private byte[]? m_headerBytes;
    private byte[]? m_protectiveMbrBytes;
    private long m_bytesPerSector;

    // Partition storage
    private List<IPartition>? m_partitions;

    /// <inheritdoc />
    public IRawDisk? RawDisk { get => m_rawDisk; }

    /// <inheritdoc />
    public PartitionTableType TableType => PartitionTableType.GPT;

    /// <summary>
    /// Initializes a new instance of the <see cref="GPT"/> class.
    /// </summary>
    /// <param name="disk">The raw disk to parse, or null for byte array parsing.</param>
    public GPT(IRawDisk? disk)
    {
        m_rawDisk = disk;
    }

    /// <summary>
    /// Parses the GPT partition table from the raw disk.
    /// </summary>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    public async Task<bool> ParseAsync(CancellationToken token)
    {
        var parsedHeader = await ParseHeaderAsync(token)
            .ConfigureAwait(false);

        if (!parsedHeader)
            return false;

        return await ParsePartitionEntriesAsync(token)
            .ConfigureAwait(false);
    }

    /// <summary>
    /// Parses the GPT partition table from a byte array.
    /// </summary>
    /// <param name="bytes">The raw disk bytes.</param>
    /// <param name="sectorSize">The sector size in bytes.</param>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    public async Task<bool> ParseAsync(byte[] bytes, int sectorSize, CancellationToken token)
    {
        m_bytesPerSector = sectorSize;

        // Parse the protective MBR first (LBA 0; first sectorSize bytes)
        if (!await ParseProtectiveMbrAsync(bytes, sectorSize, token).ConfigureAwait(false))
            return false;

        // Now parse the GPT header (LBA 1) - use span slicing to avoid allocation
        var parsedHeader = await ParseHeaderAsync(bytes.AsSpan(sectorSize, HeaderSize), token).ConfigureAwait(false);

        if (!parsedHeader)
            return false;

        // Calculate the byte offset for the partition entries
        int partitionEntriesOffset = (int)(m_partitionEntryLba * m_bytesPerSector);
        int sizeEntries = (int)(m_partitionEntrySize * m_numPartitionEntries);

        var partitionBytes = bytes[partitionEntriesOffset..(partitionEntriesOffset + sizeEntries)];

        return await ParsePartitionEntriesAsync(partitionBytes, token).ConfigureAwait(false);
    }

    /// <summary>
    /// Parses the protective MBR to verify this is a GPT disk.
    /// </summary>
    private async Task<bool> ParseProtectiveMbrAsync(byte[] bytes, int sectorSize, CancellationToken token)
    {
        if (bytes.Length < sectorSize)
            throw new ArgumentException($"Byte array must be at least {sectorSize} bytes long.", nameof(bytes));

        // Extract the MBR (first sector)
        m_protectiveMbrBytes = bytes[0..sectorSize];

        // Verify MBR boot signature (offset 510)
        ushort bootSignature = BinaryPrimitives.ReadUInt16LittleEndian(m_protectiveMbrBytes.AsSpan(510));
        if (bootSignature != MbrBootSignature)
            return false;

        // Check if first partition entry has protective MBR type (0xEE)
        byte partitionType = m_protectiveMbrBytes[450];
        if (partitionType != ProtectiveMbrType)
            return false;

        return true;
    }

    /// <summary>
    /// Parses the GPT header from the raw disk.
    /// </summary>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    public async Task<bool> ParseHeaderAsync(CancellationToken token)
    {
        if (m_rawDisk == null)
            throw new InvalidOperationException("No raw disk available for reading GPT header.");

        m_bytesPerSector = m_rawDisk.SectorSize;

        // Read the GPT header (LBA 1) directly into a pooled buffer
        // Rent a sector-sized buffer, read into it, then copy just the header portion
        var sectorBuffer = ArrayPool<byte>.Shared.Rent((int)m_bytesPerSector);
        try
        {
            int bytesRead = await m_rawDisk.ReadBytesAsync(m_bytesPerSector, sectorBuffer.AsMemory(0, (int)m_bytesPerSector), token)
                .ConfigureAwait(false);

            if (bytesRead < HeaderSize)
                return false;

            // Copy header data to the long-lived header buffer
            m_headerBytes = new byte[HeaderSize];
            sectorBuffer.AsSpan(0, HeaderSize).CopyTo(m_headerBytes);

            var result = await ParseHeaderAsync(m_headerBytes.AsSpan(), token)
                .ConfigureAwait(false);

            if (result)
            {
                // Verify backup header
                if (!await VerifyBackupHeaderAsync(token).ConfigureAwait(false))
                    return false;
            }

            return result;
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(sectorBuffer);
        }
    }

    /// <summary>
    /// Parses the GPT header from a byte array.
    /// </summary>
    /// <param name="bytes">The header bytes.</param>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    public async Task<bool> ParseHeaderAsync(byte[] bytes, CancellationToken token)
        => await ParseHeaderAsync(bytes.AsSpan(), token).ConfigureAwait(false);

    /// <summary>
    /// Parses the GPT header from a span of bytes.
    /// </summary>
    /// <param name="bytes">The header bytes.</param>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    public Task<bool> ParseHeaderAsync(ReadOnlySpan<byte> bytes, CancellationToken token)
    {
        if (bytes.Length < HeaderSize)
            throw new ArgumentException($"Byte array must be at least {HeaderSize} bytes long.", nameof(bytes));

        // Read signature (8 bytes, little-endian)
        m_signature = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(0, 8));

        // Verify signature
        if (m_signature != GptSignature)
            return Task.FromResult(false);

        // Read revision (4 bytes)
        m_revision = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(8, 4));

        // Read header size (4 bytes)
        m_headerSize = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(12, 4));

        // Read header CRC32 (4 bytes)
        m_headerCrc32 = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(16, 4));

        // Reserved - must be zero (4 bytes at offset 20)
        var reserved = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(20, 4));
        if (reserved != 0)
            return Task.FromResult(false);

        // Current LBA (8 bytes at offset 24)
        m_currentLba = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(24, 8));

        // Backup LBA (8 bytes at offset 32)
        m_backupLba = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(32, 8));

        // First usable LBA (8 bytes at offset 40)
        m_firstUsableLba = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(40, 8));

        // Last usable LBA (8 bytes at offset 48)
        m_lastUsableLba = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(48, 8));

        // Disk GUID (16 bytes at offset 56)
        m_diskGuid = new Guid(bytes.Slice(56, 16));

        // Partition entry LBA (8 bytes at offset 72)
        m_partitionEntryLba = BinaryPrimitives.ReadInt64LittleEndian(bytes.Slice(72, 8));

        // Number of partition entries (4 bytes at offset 80)
        m_numPartitionEntries = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(80, 4));

        // Size of partition entry (4 bytes at offset 84)
        m_partitionEntrySize = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(84, 4));

        // Partition entry CRC32 (4 bytes at offset 88)
        m_partitionEntryCrc32 = BinaryPrimitives.ReadUInt32LittleEndian(bytes.Slice(88, 4));

        m_parsed = true;
        return Task.FromResult(true);
    }

    /// <summary>
    /// Parses the partition entries from the disk.
    /// </summary>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the raw disk is not available or if the header has not been parsed.</exception>
    private async Task<bool> ParsePartitionEntriesAsync(CancellationToken token)
    {
        if (m_rawDisk == null)
            throw new InvalidOperationException("No raw disk available for reading GPT partition entries.");

        if (m_partitions != null)
            return false;

        if (!m_parsed)
            return false;

        // Calculate the byte offset for the partition entries
        long partitionEntriesOffset = m_partitionEntryLba * m_bytesPerSector;

        // Read all partition entries in one go
        long totalSize = m_partitionEntrySize * m_numPartitionEntries;

        // Rent buffer from ArrayPool to avoid allocation for partition entries
        var buffer = ArrayPool<byte>.Shared.Rent((int)totalSize);
        try
        {
            using var stream = await m_rawDisk.ReadBytesAsync(partitionEntriesOffset, (int)totalSize, token)
                .ConfigureAwait(false);
            await stream.ReadAtLeastAsync(buffer, (int)totalSize, cancellationToken: token)
                .ConfigureAwait(false);

            return await ParsePartitionEntriesAsync(buffer.AsSpan(0, (int)totalSize), token)
                .ConfigureAwait(false);
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }

    /// <summary>
    /// Parses the partition entries from a byte array.
    /// </summary>
    /// <param name="buffer">The byte array containing the partition entries.</param>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    private Task<bool> ParsePartitionEntriesAsync(byte[] buffer, CancellationToken token)
        => ParsePartitionEntriesAsync(buffer.AsSpan(), token);

    /// <summary>
    /// Parses the partition entries from a span of bytes.
    /// </summary>
    /// <param name="buffer">The span containing the partition entries.</param>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if parsing was successful.</returns>
    private Task<bool> ParsePartitionEntriesAsync(ReadOnlySpan<byte> buffer, CancellationToken token)
    {
        if (m_partitions != null)
            return Task.FromResult(false);

        m_partitions = [];

        // Parse each partition entry
        for (int i = 0; i < m_numPartitionEntries; i++)
        {
            token.ThrowIfCancellationRequested();

            int offset = (int)(i * m_partitionEntrySize);

            // Check if this entry is empty (all zeros in the first 16 bytes = partition type GUID)
            bool isEmpty = true;
            for (int j = 0; j < 16; j++)
            {
                if (buffer[offset + j] != 0)
                {
                    isEmpty = false;
                    break;
                }
            }

            if (isEmpty)
                continue;

            // Parse partition entry - use span slicing to avoid allocation
            var partition = ParsePartitionEntry(buffer.Slice(offset, (int)m_partitionEntrySize), i + 1);
            if (partition != null)
                m_partitions.Add(partition!);
        }

        return Task.FromResult(true);
    }

    /// <summary>
    /// Parses a single partition entry from a byte array.
    /// </summary>
    /// <param name="buffer">The byte array containing the partition entry.</param>
    /// <param name="offset">The offset in the byte array where the partition entry starts.</param>
    /// <param name="partitionNumber">The partition number.</param>
    /// <returns>The parsed partition entry, or null if the entry is empty.</returns>
    private BasePartition? ParsePartitionEntry(byte[] buffer, int offset, int partitionNumber)
        => ParsePartitionEntry(buffer.AsSpan(offset), partitionNumber);

    /// <summary>
    /// Parses a single partition entry from a span of bytes.
    /// </summary>
    /// <param name="entrySpan">The span containing the partition entry.</param>
    /// <param name="partitionNumber">The partition number.</param>
    /// <returns>The parsed partition entry, or null if the entry is empty.</returns>
    private BasePartition? ParsePartitionEntry(ReadOnlySpan<byte> entrySpan, int partitionNumber)
    {
        // Partition type GUID (16 bytes at offset 0)
        var typeGuid = new Guid(entrySpan.Slice(0, 16));

        // Unique partition GUID (16 bytes at offset 16)
        var uniqueGuid = new Guid(entrySpan.Slice(16, 16));

        // Starting LBA (8 bytes at offset 32)
        long startingLba = BinaryPrimitives.ReadInt64LittleEndian(entrySpan.Slice(32, 8));

        // Ending LBA (8 bytes at offset 40)
        long endingLba = BinaryPrimitives.ReadInt64LittleEndian(entrySpan.Slice(40, 8));

        // Attributes (8 bytes at offset 48)
        long attributes = BinaryPrimitives.ReadInt64LittleEndian(entrySpan.Slice(48, 8));

        // Partition name (36 UTF-16LE characters = 72 bytes at offset 56)
        string name = System.Text.Encoding.Unicode.GetString(entrySpan.Slice(56, 72)).TrimEnd('\0');

        // Calculate byte offsets and size
        long startOffset = startingLba * m_bytesPerSector;
        long size = (endingLba - startingLba + 1) * m_bytesPerSector;

        // Determine partition type based on type GUID
        PartitionType partitionType = DeterminePartitionType(typeGuid);

        // Determine filesystem type based on partition name and known patterns
        FileSystemType fsType = DetermineFilesystemType(startOffset, (int)size, typeGuid.ToString(), CancellationToken.None);

        return new BasePartition
        {
            PartitionNumber = partitionNumber,
            Type = partitionType,
            PartitionTable = this,
            StartOffset = startOffset,
            Size = size,
            Name = string.IsNullOrEmpty(name) ? null : name,
            FilesystemType = fsType,
            VolumeGuid = uniqueGuid,
            RawDisk = m_rawDisk,
            StartingLba = startingLba,
            EndingLba = endingLba,
            Attributes = attributes
        };
    }

    /// <summary>
    /// Determines the partition type based on the partition type GUID.
    /// </summary>
    /// <param name="typeGuid">The partition type GUID.</param>
    /// <returns>The corresponding <see cref="PartitionType"/>.</returns>
    private static PartitionType DeterminePartitionType(Guid typeGuid)
    {
        return GptPartitionTypeGuids.ToPartitionType(typeGuid);
    }

    /// <summary>
    /// Determines the filesystem type based on the partition type GUID and partition data.
    /// </summary>
    /// <param name="name">The partition name.</param>
    /// <param name="typeGuid">The partition type GUID.</param>
    /// <returns>The corresponding <see cref="FileSystemType"/>.</returns>
    private FileSystemType DetermineFilesystemType(long offset, int size, string typeGuid, CancellationToken cancellationToken)
    {
        // First, we try to look at the GUID
        if (typeGuid == "48465300-0000-11AA-AA11-00306543ECAC")
            return FileSystemType.HFSPlus;

        if (typeGuid == "7C3457EF-0000-11AA-AA11-00306543ECAC")
            return FileSystemType.APFS;

        if (m_rawDisk is not null)
        {
            if (Fat32Filesystem.DetectAsync(m_rawDisk, offset, size, cancellationToken).Await())
                return FileSystemType.FAT32;
        }

        return FileSystemType.Unknown;
    }

    // GPT-specific properties
    /// <summary>
    /// Gets the GPT signature ("EFI PART" in little-endian).
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long Signature => m_parsed ? m_signature : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the GPT revision number.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint Revision => m_parsed ? m_revision : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the GPT header size in bytes.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint HeaderSizeField => m_parsed ? m_headerSize : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the CRC32 checksum of the GPT header.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint HeaderCrc32 => m_parsed ? m_headerCrc32 : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the LBA (Logical Block Address) of the current GPT header.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long CurrentLba => m_parsed ? m_currentLba : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the LBA of the backup GPT header.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long BackupLba => m_parsed ? m_backupLba : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the first usable LBA for partitions.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long FirstUsableLba => m_parsed ? m_firstUsableLba : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the last usable LBA for partitions.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long LastUsableLba => m_parsed ? m_lastUsableLba : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the disk GUID.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public Guid DiskGuid => m_parsed ? m_diskGuid : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the LBA where partition entries start.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public long PartitionEntryLba => m_parsed ? m_partitionEntryLba : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the number of partition entries.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint NumPartitionEntries => m_parsed ? m_numPartitionEntries : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the size of each partition entry in bytes.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint PartitionEntrySizeField => m_parsed ? m_partitionEntrySize : throw new InvalidOperationException("GPT header not parsed.");

    /// <summary>
    /// Gets the CRC32 checksum of the partition entries.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if GPT header has not been parsed.</exception>
    public uint PartitionEntryCrc32 => m_parsed ? m_partitionEntryCrc32 : throw new InvalidOperationException("GPT header not parsed.");

    /// <inheritdoc />
    public async IAsyncEnumerable<IPartition> EnumeratePartitions([System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
    {
        if (!m_parsed)
            throw new InvalidOperationException("GPT header not parsed.");

        // Ensure partitions are parsed
        if (m_partitions == null && m_rawDisk != null)
            await ParsePartitionEntriesAsync(cancellationToken).ConfigureAwait(false);

        if (m_partitions != null)
        {
            foreach (var partition in m_partitions)
            {
                cancellationToken.ThrowIfCancellationRequested();
                yield return partition;
            }
        }
    }

    /// <inheritdoc />
    public async Task<IPartition?> GetPartitionAsync(int partitionNumber, CancellationToken cancellationToken)
    {
        if (!m_parsed)
            throw new InvalidOperationException("GPT header not parsed.");

        // Ensure partitions are parsed
        if (m_partitions == null && m_rawDisk != null)
            await ParsePartitionEntriesAsync(cancellationToken).ConfigureAwait(false);

        if (m_partitions == null)
            return null;

        if (partitionNumber >= 1 && partitionNumber <= m_partitions.Count)
            return m_partitions[partitionNumber - 1];

        return null;
    }

    /// <inheritdoc />
    public async Task<Stream> GetProtectiveMbrAsync(CancellationToken cancellationToken)
    {
        if (!m_parsed)
            throw new InvalidOperationException("GPT header not parsed.");

        if (m_protectiveMbrBytes != null)
        {
            // Return a MemoryStream with the stored MBR bytes
            return new MemoryStream(m_protectiveMbrBytes, writable: false);
        }

        if (m_rawDisk != null)
        {
            // Read MBR from disk
            return await m_rawDisk.ReadBytesAsync(0, MbrSize, cancellationToken).ConfigureAwait(false);
        }

        throw new InvalidOperationException("No protective MBR available.");
    }

    /// <inheritdoc />
    public async Task<Stream> GetPartitionTableDataAsync(CancellationToken cancellationToken)
    {
        if (!m_parsed)
            throw new InvalidOperationException("GPT header not parsed.");

        if (m_rawDisk == null)
            throw new InvalidOperationException("No raw disk available for reading GPT data.");

        // Calculate the total size needed:
        // - Protective MBR (512 bytes)
        // - GPT Header (1 sector)
        // - Partition Entries (m_partitionEntryLba sectors)
        long partitionEntriesEnd = m_partitionEntryLba * m_bytesPerSector + (m_numPartitionEntries * m_partitionEntrySize);
        long totalSize = partitionEntriesEnd;

        // Read all the data
        using var stream = await m_rawDisk.ReadBytesAsync(0, (int)totalSize, cancellationToken).ConfigureAwait(false);
        var buffer = new byte[totalSize];
        await stream.ReadAtLeastAsync(buffer, (int)totalSize, cancellationToken: cancellationToken).ConfigureAwait(false);

        return new MemoryStream(buffer, writable: false);
    }

    /// <summary>
    /// Verifies the backup GPT header.
    /// </summary>
    /// <param name="token">Cancellation token.</param>
    /// <returns>True if the backup header is valid, false otherwise.</returns>
    private async Task<bool> VerifyBackupHeaderAsync(CancellationToken token)
    {
        if (m_rawDisk == null)
            throw new InvalidOperationException("No raw disk available for reading GPT backup header.");

        if (m_backupLba == 0)
            return false;

        var backupHeaderBytes = new byte[HeaderSize];
        long backupOffset = m_backupLba * m_bytesPerSector;

        try
        {
            using var stream = await m_rawDisk.ReadBytesAsync(backupOffset, (int)m_bytesPerSector, token).ConfigureAwait(false);
            await stream.ReadAtLeastAsync(backupHeaderBytes, HeaderSize, cancellationToken: token).ConfigureAwait(false);
        }
        catch
        {
            return false;
        }

        // Verify Signature
        long signature = BinaryPrimitives.ReadInt64LittleEndian(backupHeaderBytes.AsSpan(0, 8));
        if (signature != GptSignature)
            return false;

        // Verify CRC32
        uint storedCrc = BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(16, 4));

        // Zero out CRC field for calculation
        var copyForCrc = new byte[HeaderSize];
        Array.Copy(backupHeaderBytes, copyForCrc, HeaderSize);
        BinaryPrimitives.WriteUInt32LittleEndian(copyForCrc.AsSpan(16), 0u);

        uint calculatedCrc = Crc32.Calculate(copyForCrc, 0, HeaderSize);
        if (storedCrc != calculatedCrc)
            return false;

        // Verify other fields
        // Revision should be same
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(8, 4)) != m_revision) return false;
        // Header size should be same
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(12, 4)) != m_headerSize) return false;
        // Reserved should be 0
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(20, 4)) != 0) return false;

        // Current LBA should be Backup LBA
        if (BinaryPrimitives.ReadInt64LittleEndian(backupHeaderBytes.AsSpan(24, 8)) != m_backupLba) return false;
        // Backup LBA should be Current LBA (Primary LBA)
        if (BinaryPrimitives.ReadInt64LittleEndian(backupHeaderBytes.AsSpan(32, 8)) != m_currentLba) return false;

        // Usable LBAs should be same
        if (BinaryPrimitives.ReadInt64LittleEndian(backupHeaderBytes.AsSpan(40, 8)) != m_firstUsableLba) return false;
        if (BinaryPrimitives.ReadInt64LittleEndian(backupHeaderBytes.AsSpan(48, 8)) != m_lastUsableLba) return false;

        // Disk GUID should be same
        if (new Guid(backupHeaderBytes.AsSpan(56, 16)) != m_diskGuid) return false;

        // Number of partition entries should be same
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(80, 4)) != m_numPartitionEntries) return false;
        // Size of partition entry should be same
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(84, 4)) != m_partitionEntrySize) return false;
        // Partition entry CRC32 should be same
        if (BinaryPrimitives.ReadUInt32LittleEndian(backupHeaderBytes.AsSpan(88, 4)) != m_partitionEntryCrc32) return false;

        return true;
    }

    /// <inheritdoc />
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Disposes the GPT instance, releasing any resources. After disposal, the instance should not be used.
    /// </summary>
    /// <param name="disposing">Indicates whether the method is called from Dispose (true) or from a finalizer (false).</param>
    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (disposing)
            {
                m_headerBytes = null;
                m_protectiveMbrBytes = null;
                if (m_partitions != null)
                {
                    foreach (var partition in m_partitions)
                        partition.Dispose();
                    m_partitions = null;
                }
            }
            m_disposed = true;
        }
    }

}
