using CashLineERP.Application.Pricing;
using CashLineERP.Domain.Sales;
using CashLineERP.Domain.Workflow;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class PricingRuleRepository : IPricingRuleRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public PricingRuleRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReadOnlyCollection<PricingRuleDetailView> GetPricingRules(bool activeOnly = false) =>
        _dbContext.PricingRules
            .AsNoTracking()
            .Where(rule => !activeOnly || rule.IsActive)
            .OrderByDescending(rule => rule.Priority)
            .ThenBy(rule => rule.RuleCode)
            .ToArray()
            .Select(Map)
            .ToArray();

    public PricingRuleDetailView? GetPricingRule(long pricingRuleId)
    {
        var rule = _dbContext.PricingRules
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == pricingRuleId);

        return rule is null ? null : Map(rule);
    }

    public PricingRuleDetailView Create(CreatePricingRuleCommand command)
    {
        var nextSequence = (_dbContext.PricingRules.Max(rule => (long?)rule.Id) ?? 0L) + 1L;
        var normalizedCurrencyCode = command.CurrencyCode.Trim().ToUpperInvariant();
        var normalizedUnitOfMeasureCode = command.UnitOfMeasureCode.Trim().ToUpperInvariant();

        if (command.FixedUnitPrice <= 0m)
        {
            throw new InvalidOperationException("Pricing rule fixed unit price must be greater than zero.");
        }

        if (command.MinimumQuantity.HasValue && command.MaximumQuantity.HasValue && command.MinimumQuantity.Value > command.MaximumQuantity.Value)
        {
            throw new InvalidOperationException("Pricing rule minimum quantity cannot exceed maximum quantity.");
        }

        var rule = new PricingRule(
            ruleCode: $"PR-{nextSequence:D6}",
            ruleName: command.RuleName.Trim(),
            branchId: command.BranchId,
            customerId: command.CustomerId,
            itemId: command.ItemId,
            currencyCode: normalizedCurrencyCode,
            unitOfMeasureCode: normalizedUnitOfMeasureCode,
            minimumQuantity: command.MinimumQuantity,
            maximumQuantity: command.MaximumQuantity,
            fixedUnitPrice: command.FixedUnitPrice,
            priority: command.Priority,
            effectiveFromDate: command.EffectiveFromDate,
            effectiveToDate: command.EffectiveToDate,
            notes: command.Notes);

        _dbContext.PricingRules.Add(rule);
        _dbContext.SaveChanges();
        return GetPricingRule(rule.Id)!;
    }

    public PricingRuleDetailView Submit(long pricingRuleId, long actorUserId)
    {
        var rule = _dbContext.PricingRules.Single(entry => entry.Id == pricingRuleId);
        rule.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "SALES",
            documentType: "PRICING_RULE",
            documentId: rule.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Pricing rule submitted for approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetPricingRule(rule.Id)!;
    }

    public PricingRuleDetailView Approve(long pricingRuleId, long actorUserId)
    {
        var rule = _dbContext.PricingRules.Single(entry => entry.Id == pricingRuleId);
        rule.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(entry => entry.Actions)
            .SingleOrDefault(entry => entry.DocumentType == "PRICING_RULE" && entry.DocumentId == pricingRuleId);
        workflowInstance?.Approve(actorUserId, "Pricing rule approved.");

        _dbContext.SaveChanges();
        return GetPricingRule(rule.Id)!;
    }

    public PricingRuleDetailView Activate(long pricingRuleId, long actorUserId)
    {
        var rule = _dbContext.PricingRules.Single(entry => entry.Id == pricingRuleId);
        rule.Activate(actorUserId);
        _dbContext.SaveChanges();
        return GetPricingRule(rule.Id)!;
    }

    public PricingEvaluationView Evaluate(PricingEvaluationCommand command)
    {
        var normalizedCurrencyCode = command.CurrencyCode.Trim().ToUpperInvariant();
        var activeRules = _dbContext.PricingRules
            .AsNoTracking()
            .ToArray();

        var lines = command.Lines.Select(line =>
        {
            if (line.RequestedUnitPrice.HasValue && line.RequestedUnitPrice.Value > 0m)
            {
                return new PricingEvaluationLineView(
                    line.ItemId,
                    line.OrderedQuantity,
                    line.UnitOfMeasureCode,
                    line.RequestedUnitPrice.Value,
                    "MANUAL",
                    null,
                    null,
                    null,
                    null,
                    "Manual unit price provided by caller.");
            }

            var matchedRule = activeRules
                .Where(rule => rule.AppliesTo(
                    command.BranchId,
                    command.CustomerId,
                    line.ItemId,
                    normalizedCurrencyCode,
                    line.UnitOfMeasureCode.Trim().ToUpperInvariant(),
                    line.OrderedQuantity,
                    command.PricingDate))
                .OrderByDescending(rule => rule.Priority)
                .ThenByDescending(rule => rule.GetSpecificityScore())
                .ThenByDescending(rule => rule.EffectiveFromDate)
                .ThenBy(rule => rule.Id)
                .FirstOrDefault();

            if (matchedRule is null)
            {
                return new PricingEvaluationLineView(
                    line.ItemId,
                    line.OrderedQuantity,
                    line.UnitOfMeasureCode,
                    0m,
                    "UNRESOLVED",
                    null,
                    null,
                    null,
                    null,
                    "No active pricing rule matched the requested sales context.");
            }

            return new PricingEvaluationLineView(
                line.ItemId,
                line.OrderedQuantity,
                line.UnitOfMeasureCode,
                matchedRule.FixedUnitPrice,
                "RULE",
                matchedRule.Id,
                matchedRule.RuleCode,
                matchedRule.RuleName,
                matchedRule.Priority,
                matchedRule.Notes);
        }).ToArray();

        return new PricingEvaluationView(
            command.BranchId,
            command.CustomerId,
            command.PricingDate,
            normalizedCurrencyCode,
            lines);
    }

    private static PricingRuleDetailView Map(PricingRule rule) =>
        new(
            rule.Id,
            rule.RuleCode,
            rule.RuleName,
            rule.BranchId,
            rule.CustomerId,
            rule.ItemId,
            rule.CurrencyCode,
            rule.UnitOfMeasureCode,
            rule.MinimumQuantity,
            rule.MaximumQuantity,
            rule.FixedUnitPrice,
            rule.Priority,
            rule.EffectiveFromDate,
            rule.EffectiveToDate,
            WorkflowStatusSeedIds.ToCode(rule.StatusId),
            rule.IsActive,
            rule.Notes,
            rule.SubmittedAtUtc,
            rule.SubmittedByUserId,
            rule.ApprovedAtUtc,
            rule.ApprovedByUserId,
            rule.ActivatedAtUtc,
            rule.ActivatedByUserId);
}
