using AyCode.Core.Enums;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Serializers.Toons;
using FruitBank.Common;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Loggers;
using FruitBankHybrid.Shared.Services.SignalRs;
using Mango.Nop.Core.Entities;
using Newtonsoft.Json;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using System.Linq.Expressions;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Serialization;
using System.Text;

namespace FruitBankHybrid.Shared.Tests;

//1. "Headered List" (A biztonságos táblázatosítás)

//Az LLM-eknek nem kell minden sorban megismételni a mezőneveket, ha a lista elején egyszer definiálod a sorrendet.Ez nem találgatás, hanem egy lokális "szerződés".

//Hagyományos (pazarló):
//Kódrészlet

//OrderItemDtos = [
//  OrderItemDto { Id = 120, Quantity = 10, ProductName = "Áfonya" }
//OrderItemDto { Id = 121, Quantity = 5, ProductName = "Narancs" }
//]

//Optimalizált(pontos és tömör) :
//Kódrészlet

//OrderItemDtos: OrderItemDto[] = [
//  [Id, Quantity, ProductName]
//  [120, 10, "Áfonya"]
//  [121, 5, "Narancs"]
//]

//    Miért jó ez? Az LLM a fejléc alapján(mint egy CSV-nél) rendeli hozzá az értékeket a típushoz.Mivel a típus (OrderItemDto) ott van a definícióban, a szemantikai kapcsolat nem vész el.

//2. Típus-öröklődés a listákban

//Ha a @types részben már leírtad, hogy az OrderItemDto.ProductDto mezője egy ProductDto típust vár, akkor a @data részben felesleges kiírni a típusnevet minden egyes elemnél.

//Példa:
//Kódrészlet

//// A 'ProductDto' elhagyható az objektum elől, mert a sémából tudja
//ProductDto = {
//    Id = 1
//  Name = "Áfonya..."
//  GenericAttributes = [
//    { Id = 99, Key = "NetWeight", Value = "178.3" }
//    { Id = 100, Key = "GrossWeight", Value = "19" }
//  ]
//}

//3. Alapértelmezett értékek elhagyása(Implicit Defaults)

//Ha egy mező értéke megegyezik a @types - ban definiált default-value-val, vagy null/0/false, akkor azt teljesen hagyd ki a @data részből.

//    Szabály: Ami nincs ott, az az alapértelmezett.

//    Token megtakarítás: A FruitBank példádban a GenericAttributes = < GenericAttributeDto[] > (count: 0)[] sorok rengeteg helyet foglalnak. Ha üres, egyszerűen ne küldd el a mezőt.

//4. String Table helyett: "Object Anchoring"

//használd az objektum-referenciákat (amit a @ProductDto:1 jelöléssel már el is kezdett a rendszered).

//Ha ugyanaz a Product szerepel 5 különböző rendelési tételnél, ne írd le ötször.

//    Első alkalommal: ProductDto { ... }

//Minden további alkalommal: ProductDto = @ProductDto:1

//[ToonIgnore][ToonDataIgnore]
[ToonDescription(Purpose = "Container model for Shipping, Order")]
public class FullProcessModel
{
    public List<Shipping> Shippings { get; set; }
    public List<OrderDto> Orders { get; set; }
    public List<PreOrder> PreOrders { get; set; }
    public List<StockTaking> StockTakings { get; set; }
    public List<EkaerHistory> EkaerHistories { get; set; }
}

[TestClass]
public sealed class ToonTests
{
    private const int CustomerIdAasdDsserverCom = 6; //aasd@dsserver.com

    private FruitBankSignalRClient _signalRClient = null!;

    [TestInitialize]
    public void TestInit()
    {
        if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!");

        _signalRClient = TestSignalRClientFactory.Create(nameof(FruitBankClientTests));
    }

#if DEBUG
    [TestMethod]
    public async Task GetAnalyzeStringInternCandidatesLog()
    {
        var orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();

        var options = AcBinarySerializerOptions.WithoutReferenceHandling;
        //options.SetReferenceHandlingUnsafe(ReferenceHandlingMode.OnlyId);
        var analysisLog = AcBinarySerializer.GetAnalyzeStringInternCandidatesLog(orders, options);
        
        Assert.IsNotNull(analysisLog);
        Assert.IsGreaterThan(0, analysisLog.Length);

        // Print results sorted by occurrence count
        Console.WriteLine(analysisLog.ToString());
        Console.WriteLine();
    }
#endif

    [TestMethod]
    public async Task OrderDtoToToon()
    {
        var a = new FullProcessModel();
        a.Orders = (await _signalRClient.GetAllOrderDtos())!.Where(x=>x.CreatedOnUtc > DateTime.UtcNow.AddDays(-70)).ToList();
        a.Shippings = (await _signalRClient.GetShippings())!.Where(x=>x.Created > DateTime.UtcNow.AddDays(-70)).ToList();

        var toon = AcToonSerializer.Serialize(a, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);
        //var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);

        Console.WriteLine(toon);
        Assert.IsNotEmpty(toon);
        // Note: @ref: only appears when the same object instance is referenced multiple times.
        // Data from separate API calls typically don't share object instances.
    }

    [TestMethod]
    public void GetMetaInfos()
    {
        var a = new FullProcessModel();
        var toon = AcToonSerializer.SerializeTypeMetadata<FullProcessModel>(FruitBankConstClient.DomainDescription);
        Console.WriteLine(toon);
    }


    [TestMethod]
    public void ReferenceHandling_WithSharedReferences_ShouldOutputRefMarkers()
    {
        // Create a simple test container with shared references
        var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
        
        // Create a container that references the same ProductDto twice
        var container = new TestContainerWithSharedRefs
        {
            Product1 = sharedProduct,
            Product2 = sharedProduct  // Same instance, should create @ref
        };

        var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);

        Console.WriteLine(toon);
        Assert.IsNotEmpty(toon);
        Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
    }

    [TestMethod]
    public void ReferenceHandling_WithSharedIIdReferences_ShouldOutputRefMarkers()
    {
        // Create a simple test container with shared references
        var sharedProduct = new ProductDto { Id = 1, Name = "Shared Product" };
        var sharedProduct2 = new ProductDto { Id = 1, Name = "Shared Product" };

        // Create a container that references the same ProductDto twice
        var container = new TestContainerWithSharedRefs
        {
            Product1 = sharedProduct,
            Product2 = sharedProduct2  // Same instance, should create @ref
        };

        var toon = AcToonSerializer.Serialize(container, FruitBankConstClient.DomainDescription, AcToonSerializerOptions.Default);

        Console.WriteLine(toon);
        Assert.IsNotEmpty(toon);
        Assert.IsTrue(toon.Contains("@ref:"), "ReferenceHandling should detect shared Product instance");
    }

    [TestMethod]
    public void ToonTypes_ShouldNotContainList1OrGenericTypeNames()
    {
        var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
        StringAssert.DoesNotMatch(toon, new System.Text.RegularExpressions.Regex(@"List`?1"), "A @meta.types vagy @types szekcióban nem szerepelhet List`1 vagy generikus típusnév.");
    }

    [TestMethod]
    public void ToonTypes_ShouldNotContainDuplicateTypeNames()
    {
        var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
        var typesLine = toon.Split('\n').FirstOrDefault(x => x.TrimStart().StartsWith("types = ["));
        Assert.IsNotNull(typesLine, "Nem található types lista a @meta szekcióban.");
        var typeNames = typesLine.Substring(typesLine.IndexOf('[') + 1, typesLine.LastIndexOf(']') - typesLine.IndexOf('[') - 1)
            .Split(',').Select(x => x.Trim(' ', '"')).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
        var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
        Assert.IsTrue(duplicates.Count == 0, $"A types listában duplikált típusnév található: {string.Join(", ", duplicates)}");
    }

    [TestMethod]
    public void ToonTypes_EachTypeShouldBeDefinedOnceInTypesSection()
    {
        var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
        var typeDefLines = toon.Split('\n').Where(x => x.TrimEnd().EndsWith(": \"Object of type") || x.TrimEnd().EndsWith(": enum") || x.TrimEnd().EndsWith(": \"Object of type "));
        var typeNames = typeDefLines.Select(x => x.Trim().Split(':')[0]).ToList();
        var duplicates = typeNames.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key).ToList();
        Assert.IsTrue(duplicates.Count == 0, $"A @types szekcióban duplikált típusdefiníció található: {string.Join(", ", duplicates)}");
    }

    [TestMethod]
    public void ToonTypes_PropertyTypesShouldNotReferenceList1()
    {
        var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
        var lines = toon.Split('\n');
        foreach (var line in lines)
        {
            if (line.Trim().EndsWith(": List`1"))
            {
                Assert.Fail($"Property List`1 típusra hivatkozik: {line.Trim()}");
            }
        }
    }

    [TestMethod]
    public void ToonTypes_PropertyDescriptions_ShouldNotBeRedundantOrMisleading()
    {
        var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>(FruitBankConstClient.DomainDescription);
        var lines = toon.Split('\n');
        foreach (var line in lines)
        {
            if (line.Trim().StartsWith("description:") && line.Contains("Collection of Object for"))
            {
                Assert.Fail($"Redundáns vagy félrevezető description: {line.Trim()}");
            }
        }
    }

    [TestMethod]
    public void ToonTypes_NavigationMetadata_ShouldBeComplete()
    {
        var toon = AcToonSerializer.SerializeMetadata(FruitBankConstClient.DomainDescription, [typeof(Shipping), typeof(OrderDto), typeof(StockTaking), typeof(StockQuantityHistory), typeof(StockQuantityHistoryExt)]);
        Console.WriteLine(toon);
        Console.WriteLine("\n=== NAVIGATION METADATA ELLENŐRZÉS ===\n");

        var lines = toon.Split('\n').Select(x => x.TrimEnd()).ToList();

        // Ismerten egyirányú kapcsolatok - ezeknél nincs inverse property a másik oldalon
        // Customer: NopCommerce domain entity, nincs benne Orders kollekció
        // OrderNotes: OrderNote osztályban nincs Order navigation property
        // ProductDto: nincs benne OrderItems kollekció
        // GenericAttributes: polimorf kapcsolat ExpressionPredicate-tel, nincs inverse ÉS nincs egyértelmű FK
        // FONTOS: Csak az inverse-property hiányát engedjük! Az other-key-nek léteznie kell!
        var knownUnidirectionalNavigations = new HashSet<string>
        {
            "Customer",
            "OrderNotes", 
            "ProductDto",
            "GenericAttributes",
            "ShippingDocumentFile",
            "Pallet"
        };

        // GenericAttributes speciális eset - polimorf, nincs other-key sem
        var knownPolymorphicNavigations = new HashSet<string>
        {
            "GenericAttributes"
        };

        // FK property-k NEM tartalmazhatnak foreign-key attribútumot
        for (int i = 0; i < lines.Count; i++)
        {
            var line = lines[i].Trim();
            if (line.EndsWith("Id: int") || line.EndsWith("Id: int?"))
            {
                int j = i + 1;
                while (j < lines.Count)
                {
                    if (lines[j].StartsWith("    ") && !lines[j].StartsWith("      "))
                        break;

                    if (lines[j].StartsWith("      "))
                    {
                        var metaLine = lines[j].Trim();
                        if (metaLine.StartsWith("foreign-key:"))
                        {
                            Assert.Fail($"FK property nem tartalmazhat foreign-key attribútumot: {line} -> {metaLine}");
                        }
                    }
                    j++;
                }
            }
        }

        Console.WriteLine("✓ FK property-k nem tartalmaznak foreign-key attribútumot\n");

        // Számoljuk meg a hiányzó navigation metadatokat
        var missingMetadata = new List<string>();
        var skippedUnidirectional = new List<string>();

        for (int i = 0; i < lines.Count; i++)
        {
            var line = lines[i].Trim();

            // Navigation property-k keresése
            if (line.Contains(": ") &&
                !line.Contains(": int") && !line.Contains(": string") &&
                !line.Contains(": DateTime") && !line.Contains(": decimal") &&
                !line.Contains(": bool") && !line.Contains(": Guid") &&
                !line.Contains(": double") && !line.Contains(": float") &&
                !line.Contains("description:") && !line.Contains("purpose:") &&
                !line.Contains("navigation:") && !line.Contains("foreign-key:") &&
                !line.Contains("table-name:") && !line.Contains("constraints:") &&
                !line.Contains("inverse-property:") && !line.Contains("other-key:") &&
                !line.Contains("primary-key:") && !line.Contains("examples:") &&
                !line.Contains("@meta") && !line.Contains("@types") &&
                !line.Contains("version") && !line.Contains("format") && !line.Contains("source-code-language") &&
                !line.Contains("underlying-type:") && !line.Contains("default-value:") && !line.Contains("values:"))
            {
                var propName = line.Split(':')[0].Trim();
                if (string.IsNullOrEmpty(propName) || propName == "types") continue;

                // Következő sorok metadatainak összegyűjtése
                var metadata = new HashSet<string>();
                int j = i + 1;
                while (j < lines.Count && lines[j].StartsWith("      ") && lines[j].Trim().Contains(':'))
                {
                    var metaLine = lines[j].Trim();
                    if (metaLine.StartsWith("navigation:")) metadata.Add("navigation");
                    if (metaLine.StartsWith("foreign-key:")) metadata.Add("foreign-key");
                    if (metaLine.StartsWith("inverse-property:")) metadata.Add("inverse-property");
                    if (metaLine.StartsWith("other-key:")) metadata.Add("other-key");
                    j++;
                }

                // Ha van navigation attribútum, ellenőrizzük a szükséges metadatokat
                if (metadata.Contains("navigation"))
                {
                    var navLine = lines.Skip(i + 1).FirstOrDefault(x => x.Trim().StartsWith("navigation:"));
                    if (navLine != null)
                    {
                        var isUnidirectional = knownUnidirectionalNavigations.Contains(propName);
                        var isPolymorphic = knownPolymorphicNavigations.Contains(propName);

                        if (navLine.Contains("many-to-one"))
                        {
                            if (!metadata.Contains("foreign-key"))
                                missingMetadata.Add($"{propName} (ManyToOne): hiányzik foreign-key");
                            
                            if (!metadata.Contains("inverse-property"))
                            {
                                if (isUnidirectional)
                                    skippedUnidirectional.Add($"{propName} (ManyToOne): egyirányú kapcsolat, nincs inverse");
                                else
                                    missingMetadata.Add($"{propName} (ManyToOne): hiányzik inverse-property");
                            }
                        }
                        else if (navLine.Contains("one-to-many"))
                        {
                            // other-key: polimorf kapcsolatoknál nem kötelező
                            if (!metadata.Contains("other-key"))
                            {
                                if (isPolymorphic)
                                {
                                    skippedUnidirectional.Add($"{propName} (OneToMany): polimorf kapcsolat, nincs other-key");
                                }
                                else
                                {
                                    // DEBUG: részletes info
                                    Console.WriteLine($"\n[DEBUG] {propName} (OneToMany) - other-key hiányzik!");
                                    
                                    // Keressük meg a property típusát a Toon outputban
                                    var propTypePart = line.Split(':').LastOrDefault()?.Trim() ?? "";
                                    Console.WriteLine($"  Property type: {propTypePart}");
                                    
                                    // Ha List<X> formátum, keressük meg X-et
                                    if (propTypePart.StartsWith("List<") && propTypePart.EndsWith(">"))
                                    {
                                        var elementTypeName = propTypePart.Substring(5, propTypePart.Length - 6);
                                        Console.WriteLine($"  Element type: {elementTypeName}");
                                        
                                        // Keressük meg az element type definícióját
                                        var elementTypeDefIndex = lines.FindIndex(l => l.Trim().StartsWith($"{elementTypeName}:"));
                                        if (elementTypeDefIndex >= 0)
                                        {
                                            Console.WriteLine($"  Element type definition found at line {elementTypeDefIndex}");
                                            // Listázzuk ki az element type property-jeit amik "Id"-re végződnek
                                            for (int k = elementTypeDefIndex + 1; k < lines.Count; k++)
                                            {
                                                var propLine = lines[k];
                                                if (!propLine.StartsWith("    ")) break; // Új típus definíció
                                                if (propLine.StartsWith("      ")) continue; // Metaadat, skip
                                                
                                                var trimmed = propLine.Trim();
                                                if (trimmed.EndsWith(": int") && trimmed.Contains("Id"))
                                                {
                                                    Console.WriteLine($"    FK candidate: {trimmed}");
                                                }
                                            }
                                        }
                                    }
                                    
                                    missingMetadata.Add($"{propName} (OneToMany): hiányzik other-key");
                                }
                            }
                            
                            if (!metadata.Contains("inverse-property"))
                            {
                                if (isUnidirectional)
                                    skippedUnidirectional.Add($"{propName} (OneToMany): egyirányú kapcsolat, nincs inverse");
                                else
                                    missingMetadata.Add($"{propName} (OneToMany): hiányzik inverse-property");
                            }
                        }
                    }
                }
            }
        }

        if (skippedUnidirectional.Count > 0)
        {
            Console.WriteLine("EGYIRÁNYÚ/POLIMORF KAPCSOLATOK (nem hiba):");
            foreach (var skipped in skippedUnidirectional)
            {
                Console.WriteLine($"  ℹ {skipped}");
            }
            Console.WriteLine();
        }

        if (missingMetadata.Count > 0)
        {
            Console.WriteLine("HIÁNYZÓ METAADATOK:");
            foreach (var missing in missingMetadata)
            {
                Console.WriteLine($"  - {missing}");
            }
            Assert.Fail($"Hiányzó navigation metaadatok: {missingMetadata.Count} db");
        }

        Console.WriteLine("✓ Minden navigation property tartalmazza a szükséges metadatokat");
    }
}

/// <summary>
/// Test helper class to verify reference handling with shared object instances.
/// </summary>
public class TestContainerWithSharedRefs
{
    public ProductDto? Product1 { get; set; }
    public ProductDto? Product2 { get; set; }
}