using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CMSMicroservice.Protobuf.Protos.Products;
using Google.Protobuf.WellKnownTypes;

namespace FrontOffice.Main.Utilities;

public record Product(
    long Id,
    string Title,
    string Description,
    string FullInformation,
    string ImageUrl,
    long Price,
    int Discount = 1,
    int Rate = 0,
    int RemainingCount = 0)
{
    public IReadOnlyList<ProductGalleryImage> Gallery { get; init; }
        = [];

    public IReadOnlyList<ProductCategoryPathInfo> Categories { get; init; }
        = [];
}

public record ProductListResult(
    List<Product> Products,
    int TotalCount,
    int TotalPages,
    int CurrentPage,
    bool HasNext);

public record ProductGalleryImage(
    long ProductGalleryId,
    long ProductImageId,
    string Title,
    string ImageUrl,
    string ThumbnailUrl);

public record ProductCategoryNodeInfo(
    long Id,
    string Title,
    long? ParentId);

public record ProductCategoryPathInfo(
    long CategoryId,
    string Title,
    IReadOnlyList<ProductCategoryNodeInfo> Nodes)
{
    public string DisplayLabel => string.Join(" › ", Nodes.Select(node => node.Title));
    public ProductCategoryNodeInfo Leaf => Nodes.Last();
}

public class ProductService
{
    private readonly ConcurrentDictionary<long, CacheEntry> _cache = new();
    private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(1);
    private readonly CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient _client;

    public ProductService(CMSMicroservice.Protobuf.Protos.Products.ProductsContract.ProductsContractClient client)
    {
        _client = client;
    }

    public async Task<List<Product>> GetProductsAsync(string? query = null, long? categoryId = null, string? sortBy = null)
    {
        var result = await GetProductsPagedAsync(query: query, categoryId: categoryId, sortBy: sortBy, page: 1, pageSize: 500);
        return result.Products;
    }

    public Task<ProductListResult> GetTopSellingAsync(int count = 6, bool? inStock = null)
        => GetProductsPagedAsync(sortBy: "SaleCount desc", page: 1, pageSize: count, inStock: inStock);

    public async Task<ProductListResult> GetProductsPagedAsync(
        string? query = null, long? categoryId = null, string? sortBy = null,
        int page = 1, int pageSize = 12, bool? inStock = null)
    {
        try
        {
            var request = new GetAllProductsByFilterRequest
            {
                PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
                {
                    PageNumber = page,
                    PageSize = pageSize
                },
                Filter = new GetAllProductsByFilterFilter()
            };

            // فقط Title — CMS فیلترها را AND می‌زند؛ ارسال query در همه فیلدها جستجو را می‌شکند
            if (!string.IsNullOrWhiteSpace(query))
            {
                request.Filter.Title = query.Trim();
            }

            if (categoryId is { } value)
            {
                request.Filter.CategoryId = value;
            }

            if (inStock.HasValue)
            {
                request.Filter.InStock = inStock.Value;
            }

            if (!string.IsNullOrEmpty(sortBy))
            {
                request.SortBy = sortBy;
            }

            var resp = await _client.GetAllProductsByFilterAsync(request);
            var products = MapAndCache(resp.Models);
            var totalCount = (int)(resp.MetaData?.TotalCount ?? 0);
            var totalPages = (int)(resp.MetaData?.TotalPage ?? 0);
            var hasNext = resp.MetaData?.HasNext ?? false;

            return new ProductListResult(products, totalCount, totalPages, page, hasNext);
        }
        catch
        {
            IEnumerable<Product> list = GetValidCachedProducts();
            if (!string.IsNullOrWhiteSpace(query))
            {
                var q = query.Trim();
                list = list.Where(p =>
                    p.Title.Contains(q, StringComparison.OrdinalIgnoreCase) ||
                    p.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
            }

            var all = list.OrderBy(p => p.Id).ToList();
            return new ProductListResult(all, all.Count, 1, 1, false);
        }
    }

    public async Task<Product?> GetByIdAsync(long id)
    {
        if (id <= 0)
            return null;

        TryGetCachedProduct(id, out var cached);

        if (cached is not null && HasDetailedData(cached))
            return cached;

        // جزئیات کامل (گالری + دسته‌بندی)
        var detailed = await TryFetchDetailAsync(id);
        if (detailed is not null)
            return detailed;

        // fallback: همان API لیست محصولات — ناموجودها را هم برمی‌گرداند
        var fromFilter = await TryFetchByFilterIdAsync(id);
        if (fromFilter is not null)
            return fromFilter;

        return cached;
    }

    private async Task<Product?> TryFetchDetailAsync(long id)
    {
        try
        {
            var resp = await _client.GetProductsAsync(new GetProductsRequest { Id = id });
            if (resp is null || resp.Id <= 0)
                return null;

            return MapAndCache(resp);
        }
        catch
        {
            return null;
        }
    }

    private async Task<Product?> TryFetchByFilterIdAsync(long id)
    {
        try
        {
            var resp = await _client.GetAllProductsByFilterAsync(new GetAllProductsByFilterRequest
            {
                PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState
                {
                    PageNumber = 1,
                    PageSize = 1
                },
                Filter = new GetAllProductsByFilterFilter { Id = id }
            });

            var model = resp.Models.FirstOrDefault(m => m.Id == id);
            if (model is null)
                return null;

            return MapAndCache(resp.Models).FirstOrDefault(p => p.Id == id);
        }
        catch
        {
            return null;
        }
    }

    private List<Product> MapAndCache(
        Google.Protobuf.Collections.RepeatedField<GetAllProductsByFilterResponseModel> models)
    {
        var list = new List<Product>();
        foreach (var m in models)
        {
            var p = new Product(
                Id: m.Id,
                Title: m.Title ?? string.Empty,
                Description: m.Description ?? string.Empty,
                FullInformation: m.FullInformation ?? string.Empty,
                ImageUrl: m.ImagePath,
                Price: m.Price,
                Discount: m.Discount,
                Rate: m.Rate,
                RemainingCount: m.RemainingCount
            );

            p = PreserveCachedDetails(p);
            CacheProduct(p);

            list.Add(p);
        }

        return list;
    }

    private Product PreserveCachedDetails(Product product)
    {
        if (_cache.TryGetValue(product.Id, out var entry))
        {
            var cached = entry.Product;
            var hasGallery = cached.Gallery.Count > 0;
            var hasCategories = cached.Categories.Count > 0;

            if (hasGallery || hasCategories)
            {
                product = product with
                {
                    Gallery = hasGallery ? cached.Gallery : product.Gallery,
                    Categories = hasCategories ? cached.Categories : product.Categories
                };
            }
        }

        return product;
    }

    private Product MapAndCache(GetProductsResponse model)
    {
        var gallery = model.Gallery
            .Select(item => new ProductGalleryImage(
                ProductGalleryId: item.ProductGalleryId,
                ProductImageId: item.ProductImageId,
                Title: item.Title ?? string.Empty,
                ImageUrl: BuildUrl(item.ImagePath),
                ThumbnailUrl: BuildUrl(item.ImageThumbnailPath)))
            .ToList();

        var product = new Product(
            Id: model.Id,
            Title: model.Title ?? string.Empty,
            Description: model.Description ?? string.Empty,
            FullInformation: model.FullInformation ?? string.Empty,
            ImageUrl: BuildUrl(model.ImagePath),
            Price: model.Price,
            Discount: model.Discount,
            Rate: model.Rate,
            RemainingCount: model.RemainingCount)
        {
            Gallery = gallery,
            Categories = MapCategoryPaths(model.Categories)
        };

        CacheProduct(product);
        return product;
    }

    private void CacheProduct(Product product)
    {
        var entry = new CacheEntry(product, DateTime.UtcNow.Add(CacheDuration));
        _cache.AddOrUpdate(product.Id, entry, (_, _) => entry);
    }

    private bool TryGetCachedProduct(long id, [NotNullWhen(true)] out Product? product)
    {
        if (_cache.TryGetValue(id, out var entry))
        {
            if (entry.Expiration > DateTime.UtcNow)
            {
                product = entry.Product;
                return true;
            }

            _cache.TryRemove(id, out _);
        }

        product = null;
        return false;
    }

    private IEnumerable<Product> GetValidCachedProducts()
    {
        var now = DateTime.UtcNow;
        return _cache.Values
            .Where(entry => entry.Expiration > now)
            .Select(entry => entry.Product);
    }

    private static bool HasDetailedData(Product product)
        => product.Gallery.Count > 0 || product.Categories.Count > 0;
    
    private sealed record CacheEntry(Product Product, DateTime Expiration);
    private static string BuildUrl(string? path) =>
        path ?? string.Empty;

    private static IReadOnlyList<ProductCategoryPathInfo> MapCategoryPaths(IEnumerable<ProductCategoryPath>? categories)
    {
        if (categories is null)
        {
            return Array.Empty<ProductCategoryPathInfo>();
        }

        var result = new List<ProductCategoryPathInfo>();
        foreach (var category in categories)
        {
            var nodes = category.Path
                .Select(node => new ProductCategoryNodeInfo(
                    Id: node.Id,
                    Title: node.Title ?? string.Empty,
                    ParentId: node.ParentId
                ))
                .ToList();

            if (nodes.Count == 0)
            {
                continue;
            }

            result.Add(new ProductCategoryPathInfo(
                CategoryId: category.CategoryId,
                Title: category.Title ?? string.Empty,
                Nodes: nodes));
        }

        return result;
    }
}