using System;
using System.Collections.Generic;
using System.IO;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Core.Collections;

namespace ZB.MOM.WW.CBDD.Core.Storage;

public class CollectionMetadata
{
    /// <summary>
    /// Gets or sets the collection name.
    /// </summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the root page identifier of the primary index.
    /// </summary>
    public uint PrimaryRootPageId { get; set; }

    /// <summary>
    /// Gets or sets the root page identifier of the schema chain.
    /// </summary>
    public uint SchemaRootPageId { get; set; }

    /// <summary>
    /// Gets the collection index metadata list.
    /// </summary>
    public List<IndexMetadata> Indexes { get; } = new();
}

public class IndexMetadata
{
    /// <summary>
    /// Gets or sets the index name.
    /// </summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets a value indicating whether this index enforces uniqueness.
    /// </summary>
    public bool IsUnique { get; set; }

    /// <summary>
    /// Gets or sets the index type.
    /// </summary>
    public IndexType Type { get; set; }

    /// <summary>
    /// Gets or sets indexed property paths.
    /// </summary>
    public string[] PropertyPaths { get; set; } = Array.Empty<string>();

    /// <summary>
    /// Gets or sets vector dimensions for vector indexes.
    /// </summary>
    public int Dimensions { get; set; }

    /// <summary>
    /// Gets or sets the vector similarity metric for vector indexes.
    /// </summary>
    public VectorMetric Metric { get; set; }

    /// <summary>
    /// Gets or sets the root page identifier of the index structure.
    /// </summary>
    public uint RootPageId { get; set; }
}

public sealed partial class StorageEngine
{
    /// <summary>
    /// Gets collection metadata by name.
    /// </summary>
    /// <param name="name">The collection name.</param>
    /// <returns>The collection metadata if found; otherwise, null.</returns>
    public CollectionMetadata? GetCollectionMetadata(string name)
    {
        return GetAllCollectionMetadata()
            .FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>
    /// Returns all collection metadata entries currently registered in page 1.
    /// </summary>
    public IReadOnlyList<CollectionMetadata> GetAllCollectionMetadata()
    {
        var result = new List<CollectionMetadata>();
        var buffer = new byte[PageSize];
        ReadPage(1, null, buffer);

        var header = SlottedPageHeader.ReadFrom(buffer);
        if (header.PageType != PageType.Collection || header.SlotCount == 0)
            return result;

        for (ushort i = 0; i < header.SlotCount; i++)
        {
            var slotOffset = SlottedPageHeader.Size + (i * SlotEntry.Size);
            var slot = SlotEntry.ReadFrom(buffer.AsSpan(slotOffset));
            if ((slot.Flags & SlotFlags.Deleted) != 0)
                continue;

            if (slot.Offset < SlottedPageHeader.Size || slot.Offset + slot.Length > buffer.Length)
                continue;

            if (TryDeserializeCollectionMetadata(buffer.AsSpan(slot.Offset, slot.Length), out var metadata) && metadata != null)
            {
                result.Add(metadata);
            }
        }

        return result;
    }

    /// <summary>
    /// Saves collection metadata to the metadata page.
    /// </summary>
    /// <param name="metadata">The metadata to save.</param>
    public void SaveCollectionMetadata(CollectionMetadata metadata)
    {
        using var stream = new MemoryStream();
        using var writer = new BinaryWriter(stream);

        writer.Write(metadata.Name);
        writer.Write(metadata.PrimaryRootPageId);
        writer.Write(metadata.SchemaRootPageId);
        writer.Write(metadata.Indexes.Count);
        foreach (var idx in metadata.Indexes)
        {
            writer.Write(idx.Name);
            writer.Write(idx.IsUnique);
            writer.Write((byte)idx.Type);
            writer.Write(idx.RootPageId);
            writer.Write(idx.PropertyPaths.Length);
            foreach (var path in idx.PropertyPaths)
            {
                writer.Write(path);
            }

            if (idx.Type == IndexType.Vector)
            {
                writer.Write(idx.Dimensions);
                writer.Write((byte)idx.Metric);
            }
        }

        var newData = stream.ToArray();

        var buffer = new byte[PageSize];
        ReadPage(1, null, buffer);

        var header = SlottedPageHeader.ReadFrom(buffer);
        int existingSlotIndex = -1;

        for (ushort i = 0; i < header.SlotCount; i++)
        {
            var slotOffset = SlottedPageHeader.Size + (i * SlotEntry.Size);
            var slot = SlotEntry.ReadFrom(buffer.AsSpan(slotOffset));
            if ((slot.Flags & SlotFlags.Deleted) != 0) continue;

            try
            {
                using var ms = new MemoryStream(buffer, slot.Offset, slot.Length, false);
                using var reader = new BinaryReader(ms);
                var name = reader.ReadString();

                if (string.Equals(name, metadata.Name, StringComparison.OrdinalIgnoreCase))
                {
                    existingSlotIndex = i;
                    break;
                }
            }
            catch { }
        }

        if (existingSlotIndex >= 0)
        {
            var slotOffset = SlottedPageHeader.Size + (existingSlotIndex * SlotEntry.Size);
            var slot = SlotEntry.ReadFrom(buffer.AsSpan(slotOffset));
            slot.Flags |= SlotFlags.Deleted;
            slot.WriteTo(buffer.AsSpan(slotOffset));
        }

        if (header.AvailableFreeSpace < newData.Length + SlotEntry.Size)
        {
            // Compact logic omitted as per current architecture
            throw new InvalidOperationException("Not enough space in Metadata Page (Page 1) to save collection metadata.");
        }

        int docOffset = header.FreeSpaceEnd - newData.Length;
        newData.CopyTo(buffer.AsSpan(docOffset));

        ushort slotIndex;
        if (existingSlotIndex >= 0)
        {
            slotIndex = (ushort)existingSlotIndex;
        }
        else
        {
            slotIndex = header.SlotCount;
            header.SlotCount++;
        }

        var newSlotEntryOffset = SlottedPageHeader.Size + (slotIndex * SlotEntry.Size);
        var newSlot = new SlotEntry
        {
            Offset = (ushort)docOffset,
            Length = (ushort)newData.Length,
            Flags = SlotFlags.None
        };
        newSlot.WriteTo(buffer.AsSpan(newSlotEntryOffset));

        header.FreeSpaceEnd = (ushort)docOffset;
        if (existingSlotIndex == -1)
        {
            header.FreeSpaceStart = (ushort)(SlottedPageHeader.Size + (header.SlotCount * SlotEntry.Size));
        }

        header.WriteTo(buffer);
        WritePageImmediate(1, buffer);
    }

    private static bool TryDeserializeCollectionMetadata(ReadOnlySpan<byte> rawBytes, out CollectionMetadata? metadata)
    {
        metadata = null;

        try
        {
            using var ms = new MemoryStream(rawBytes.ToArray());
            using var reader = new BinaryReader(ms);

            var collName = reader.ReadString();
            var parsed = new CollectionMetadata { Name = collName };
            parsed.PrimaryRootPageId = reader.ReadUInt32();
            parsed.SchemaRootPageId = reader.ReadUInt32();

            var indexCount = reader.ReadInt32();
            if (indexCount < 0)
                return false;

            for (int j = 0; j < indexCount; j++)
            {
                var idx = new IndexMetadata
                {
                    Name = reader.ReadString(),
                    IsUnique = reader.ReadBoolean(),
                    Type = (IndexType)reader.ReadByte(),
                    RootPageId = reader.ReadUInt32()
                };

                var pathCount = reader.ReadInt32();
                if (pathCount < 0)
                    return false;

                idx.PropertyPaths = new string[pathCount];
                for (int k = 0; k < pathCount; k++)
                    idx.PropertyPaths[k] = reader.ReadString();

                if (idx.Type == IndexType.Vector)
                {
                    idx.Dimensions = reader.ReadInt32();
                    idx.Metric = (VectorMetric)reader.ReadByte();
                }

                parsed.Indexes.Add(idx);
            }

            metadata = parsed;
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Registers all BSON keys used by a set of mappers into the global dictionary.
    /// </summary>
    /// <param name="mappers">The mappers whose keys should be registered.</param>
    public void RegisterMappers(IEnumerable<IDocumentMapper> mappers)
    {
        var allKeys = mappers.SelectMany(m => m.UsedKeys).Distinct();
        RegisterKeys(allKeys);
    }
}
