using AutoMapper;
using Microsoft.Extensions.Logging;
using PowderCoating.Application.DTOs.Company;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;

namespace PowderCoating.Infrastructure.Services;

/// <summary>
/// Manages the community formula library: sharing a company template to the platform-wide
/// library, removing it, browsing published entries, and importing an entry as a local copy.
/// </summary>
public class FormulaLibraryService : IFormulaLibraryService
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    private readonly ILogger<FormulaLibraryService> _logger;

    public FormulaLibraryService(IUnitOfWork unitOfWork, IMapper mapper, ILogger<FormulaLibraryService> logger)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _logger = logger;
    }

    /// <inheritdoc />
    public async Task<IEnumerable<FormulaLibraryCardDto>> BrowseAsync(
        int companyId, string? search, string? outputMode, string? industryHint)
    {
        var items = await _unitOfWork.FormulaLibrary.FindAsync(i => i.IsPublished);

        if (!string.IsNullOrWhiteSpace(search))
        {
            var lower = search.ToLowerInvariant();
            items = items.Where(i =>
                i.Name.ToLowerInvariant().Contains(lower) ||
                (i.Description != null && i.Description.ToLowerInvariant().Contains(lower)) ||
                (i.Tags != null && i.Tags.ToLowerInvariant().Contains(lower)) ||
                i.SourceCompanyName.ToLowerInvariant().Contains(lower));
        }

        if (!string.IsNullOrWhiteSpace(outputMode))
            items = items.Where(i => i.OutputMode == outputMode);

        if (!string.IsNullOrWhiteSpace(industryHint))
            items = items.Where(i => i.IndustryHint != null &&
                i.IndustryHint.ToLowerInvariant().Contains(industryHint.ToLowerInvariant()));

        // Load InspiredBy for attribution line
        var itemList = items.ToList();
        var inspiredByIds = itemList
            .Where(i => i.InspiredByFormulaLibraryItemId.HasValue)
            .Select(i => i.InspiredByFormulaLibraryItemId!.Value)
            .Distinct()
            .ToHashSet();

        Dictionary<int, FormulaLibraryItem> inspirations = new();
        foreach (var id in inspiredByIds)
        {
            var parent = await _unitOfWork.FormulaLibrary.GetByIdAsync(id);
            if (parent != null) inspirations[id] = parent;
        }

        // Attach navigation properties manually (PlainRepository doesn't eager-load)
        foreach (var item in itemList)
        {
            if (item.InspiredByFormulaLibraryItemId.HasValue &&
                inspirations.TryGetValue(item.InspiredByFormulaLibraryItemId.Value, out var parent))
                item.InspiredBy = parent;
        }

        // Determine which entries this company has already imported
        var imports = await _unitOfWork.FormulaLibraryImports.FindAsync(
            imp => imp.CompanyId == companyId && !imp.IsDeleted);
        var importedIds = imports.Select(imp => imp.FormulaLibraryItemId).ToHashSet();

        // Load all ratings in one query for this page of items
        var allItemIds = itemList.Select(i => i.Id).ToHashSet();
        var allRatings = await _unitOfWork.FormulaLibraryRatings.FindAsync(
            r => allItemIds.Contains(r.FormulaLibraryItemId));

        // Group counts and find the current company's vote per item
        var ratingsByItem = allRatings
            .GroupBy(r => r.FormulaLibraryItemId)
            .ToDictionary(g => g.Key, g => g.ToList());

        var dtos = _mapper.Map<List<FormulaLibraryCardDto>>(itemList);
        for (int i = 0; i < dtos.Count; i++)
        {
            dtos[i].AlreadyImported = importedIds.Contains(dtos[i].Id);
            dtos[i].IsOwnFormula = itemList[i].SourceCompanyId == companyId;

            if (ratingsByItem.TryGetValue(dtos[i].Id, out var ratings))
            {
                dtos[i].ThumbsUp   = ratings.Count(r => r.IsPositive);
                dtos[i].ThumbsDown = ratings.Count(r => !r.IsPositive);
                var myRating = ratings.FirstOrDefault(r => r.CompanyId == companyId);
                dtos[i].MyVote = myRating?.IsPositive;
            }
        }

        // Sort: thumbs-up score descending, then import count, then name
        return dtos
            .OrderByDescending(d => d.ThumbsUp - d.ThumbsDown)
            .ThenByDescending(d => d.ImportCount)
            .ThenBy(d => d.Name);
    }

    /// <inheritdoc />
    public async Task<FormulaLibraryDetailDto?> GetDetailAsync(int libraryItemId, int companyId)
    {
        var item = await _unitOfWork.FormulaLibrary.GetByIdAsync(libraryItemId);
        if (item == null || !item.IsPublished) return null;

        if (item.InspiredByFormulaLibraryItemId.HasValue)
            item.InspiredBy = await _unitOfWork.FormulaLibrary.GetByIdAsync(
                item.InspiredByFormulaLibraryItemId.Value);

        var dto = _mapper.Map<FormulaLibraryDetailDto>(item);

        var imp = await _unitOfWork.FormulaLibraryImports.FindAsync(
            i => i.CompanyId == companyId && i.FormulaLibraryItemId == libraryItemId && !i.IsDeleted);
        dto.AlreadyImported = imp.Any();

        return dto;
    }

    /// <inheritdoc />
    public async Task<int> ShareAsync(int companyId, string userId, ShareFormulaRequest request)
    {
        var template = await _unitOfWork.CustomItemTemplates.GetByIdAsync(request.CustomItemTemplateId);
        if (template == null || template.CompanyId != companyId)
            throw new InvalidOperationException("Template not found.");

        if (!CanShare(template))
            throw new InvalidOperationException("This template is not eligible to be shared.");

        // Determine "Inspired by" — if this was imported from the library
        int? inspiredById = null;
        if (template.SourceFormulaLibraryItemId.HasValue && template.IsModifiedFromSource)
            inspiredById = template.SourceFormulaLibraryItemId;

        // Get company name for attribution
        var company = await _unitOfWork.Companies.GetByIdAsync(companyId);
        var companyName = company?.CompanyName ?? "Unknown Company";

        // Re-use existing row if one exists (re-share after unshare, or update after edits)
        var existing = await _unitOfWork.FormulaLibrary.FirstOrDefaultAsync(
            f => f.SourceCustomItemTemplateId == template.Id && f.SourceCompanyId == companyId);

        if (existing != null)
        {
            CopyFromTemplate(existing, template, companyName, request);
            existing.InspiredByFormulaLibraryItemId = inspiredById;
            existing.IsPublished = true;
            existing.UpdatedAt = DateTime.UtcNow;
            await _unitOfWork.FormulaLibrary.UpdateAsync(existing);
            await _unitOfWork.CompleteAsync();
            return existing.Id;
        }

        var libraryItem = new FormulaLibraryItem
        {
            SharedByUserId = userId,
            SharedAt = DateTime.UtcNow,
            SourceCustomItemTemplateId = template.Id,
            SourceCompanyId = companyId,
            SourceCompanyName = companyName,
            InspiredByFormulaLibraryItemId = inspiredById,
            IsPublished = true,
        };
        CopyFromTemplate(libraryItem, template, companyName, request);

        await _unitOfWork.FormulaLibrary.AddAsync(libraryItem);
        await _unitOfWork.CompleteAsync();
        return libraryItem.Id;
    }

    /// <inheritdoc />
    public async Task UnshareAsync(int libraryItemId, int companyId)
    {
        var item = await _unitOfWork.FormulaLibrary.GetByIdAsync(libraryItemId);
        if (item == null || item.SourceCompanyId != companyId) return;

        item.IsPublished = false;
        item.UpdatedAt = DateTime.UtcNow;
        await _unitOfWork.FormulaLibrary.UpdateAsync(item);
        await _unitOfWork.CompleteAsync();
    }

    /// <inheritdoc />
    public async Task<int> ImportAsync(int libraryItemId, int companyId, string userId)
    {
        var libraryItem = await _unitOfWork.FormulaLibrary.GetByIdAsync(libraryItemId);
        if (libraryItem == null || !libraryItem.IsPublished)
            throw new InvalidOperationException("Library entry not found or no longer published.");

        // Return existing import if already imported
        var existingImports = await _unitOfWork.FormulaLibraryImports.FindAsync(
            i => i.CompanyId == companyId && i.FormulaLibraryItemId == libraryItemId && !i.IsDeleted);
        var existingImport = existingImports.FirstOrDefault();
        if (existingImport != null) return existingImport.ResultingCustomItemTemplateId;

        // Create a local copy as a new CustomItemTemplate
        var template = new CustomItemTemplate
        {
            CompanyId = companyId,
            Name = libraryItem.Name,
            Description = libraryItem.Description,
            OutputMode = libraryItem.OutputMode,
            FieldsJson = libraryItem.FieldsJson,
            Formula = libraryItem.Formula,
            DefaultRate = libraryItem.DefaultRate,
            RateLabel = libraryItem.RateLabel,
            Notes = libraryItem.Notes,
            DiagramImagePath = libraryItem.DiagramImagePath,
            DisplayOrder = 0,
            IsActive = true,
            SourceFormulaLibraryItemId = libraryItemId,
            IsModifiedFromSource = false,
        };

        await _unitOfWork.CustomItemTemplates.AddAsync(template);
        await _unitOfWork.CompleteAsync();

        var importRecord = new FormulaLibraryImport
        {
            CompanyId = companyId,
            FormulaLibraryItemId = libraryItemId,
            ImportedByUserId = userId,
            ImportedAt = DateTime.UtcNow,
            ResultingCustomItemTemplateId = template.Id,
        };
        await _unitOfWork.FormulaLibraryImports.AddAsync(importRecord);

        // Increment import counter
        libraryItem.ImportCount++;
        await _unitOfWork.FormulaLibrary.UpdateAsync(libraryItem);

        await _unitOfWork.CompleteAsync();
        return template.Id;
    }

    /// <inheritdoc />
    public async Task<FormulaLibraryStatusDto> GetTemplateLibraryStatusAsync(int templateId, int companyId)
    {
        var template = await _unitOfWork.CustomItemTemplates.GetByIdAsync(templateId);
        if (template == null || template.CompanyId != companyId)
            return new FormulaLibraryStatusDto { CanShare = false };

        var dto = new FormulaLibraryStatusDto { CanShare = CanShare(template) };

        // Populate import attribution
        if (template.SourceFormulaLibraryItemId.HasValue)
        {
            var source = await _unitOfWork.FormulaLibrary.GetByIdAsync(template.SourceFormulaLibraryItemId.Value);
            if (source != null)
            {
                dto.ImportedFromName = source.Name;
                dto.ImportedFromCompany = source.SourceCompanyName;
            }
        }

        // Check if this template has an active library entry
        var libraryItem = await _unitOfWork.FormulaLibrary.FirstOrDefaultAsync(
            f => f.SourceCustomItemTemplateId == templateId && f.SourceCompanyId == companyId);
        if (libraryItem != null)
        {
            dto.LibraryItemId = libraryItem.Id;
            dto.IsPublished = libraryItem.IsPublished;
        }

        return dto;
    }

    /// <inheritdoc />
    public async Task CascadeRemoveDiagramAsync(int sourceCustomItemTemplateId)
    {
        // Null out on the library item published from this template
        var libraryItem = await _unitOfWork.FormulaLibrary.FirstOrDefaultAsync(
            f => f.SourceCustomItemTemplateId == sourceCustomItemTemplateId);
        if (libraryItem != null && libraryItem.DiagramImagePath != null)
        {
            libraryItem.DiagramImagePath = null;
            libraryItem.UpdatedAt = DateTime.UtcNow;
            await _unitOfWork.FormulaLibrary.UpdateAsync(libraryItem);

            // Null out on all imported copies
            var imports = await _unitOfWork.FormulaLibraryImports.FindAsync(
                i => i.FormulaLibraryItemId == libraryItem.Id && !i.IsDeleted);
            foreach (var imp in imports)
            {
                var copy = await _unitOfWork.CustomItemTemplates.GetByIdAsync(
                    imp.ResultingCustomItemTemplateId);
                if (copy != null && copy.DiagramImagePath != null)
                {
                    copy.DiagramImagePath = null;
                    await _unitOfWork.CompleteAsync();
                }
            }
        }
    }

    /// <inheritdoc />
    public async Task<(int ThumbsUp, int ThumbsDown, bool? MyVote)> RateAsync(
        int libraryItemId, int companyId, bool isPositive)
    {
        var item = await _unitOfWork.FormulaLibrary.GetByIdAsync(libraryItemId);
        if (item == null || !item.IsPublished)
            throw new InvalidOperationException("Library entry not found.");

        // Companies cannot rate their own formula
        if (item.SourceCompanyId == companyId)
            throw new InvalidOperationException("You cannot rate your own formula.");

        var existing = await _unitOfWork.FormulaLibraryRatings.FirstOrDefaultAsync(
            r => r.FormulaLibraryItemId == libraryItemId && r.CompanyId == companyId);

        bool? myVote;
        if (existing != null && existing.IsPositive == isPositive)
        {
            // Same vote again — toggle off
            await _unitOfWork.FormulaLibraryRatings.DeleteAsync(existing);
            myVote = null;
        }
        else if (existing != null)
        {
            // Opposite vote — flip it
            existing.IsPositive = isPositive;
            existing.RatedAt = DateTime.UtcNow;
            await _unitOfWork.FormulaLibraryRatings.UpdateAsync(existing);
            myVote = isPositive;
        }
        else
        {
            // New vote
            await _unitOfWork.FormulaLibraryRatings.AddAsync(new FormulaLibraryRating
            {
                FormulaLibraryItemId = libraryItemId,
                CompanyId = companyId,
                IsPositive = isPositive,
                RatedAt = DateTime.UtcNow,
            });
            myVote = isPositive;
        }

        await _unitOfWork.CompleteAsync();

        // Return fresh counts
        var allRatings = await _unitOfWork.FormulaLibraryRatings.FindAsync(
            r => r.FormulaLibraryItemId == libraryItemId);
        var list = allRatings.ToList();
        return (list.Count(r => r.IsPositive), list.Count(r => !r.IsPositive), myVote);
    }

    // ── Helpers ───────────────────────────────────────────────────────────

    /// <summary>
    /// A template is shareable if it was created fresh (no source library item) or
    /// if it was imported but then modified by the company.
    /// </summary>
    private static bool CanShare(CustomItemTemplate t) =>
        t.SourceFormulaLibraryItemId == null || t.IsModifiedFromSource;

    private static void CopyFromTemplate(
        FormulaLibraryItem dest, CustomItemTemplate src, string companyName, ShareFormulaRequest req)
    {
        dest.Name = src.Name;
        dest.Description = src.Description;
        dest.OutputMode = src.OutputMode;
        dest.FieldsJson = src.FieldsJson;
        dest.Formula = src.Formula;
        dest.DefaultRate = src.DefaultRate;
        dest.RateLabel = src.RateLabel;
        dest.Notes = src.Notes;
        dest.DiagramImagePath = src.DiagramImagePath;
        dest.SourceCompanyName = companyName;
        dest.Tags = req.Tags?.Trim();
        dest.IndustryHint = req.IndustryHint?.Trim();
    }
}
