using CashLineERP.Application.MasterData;
using CashLineERP.Domain.MasterData;
using CashLineERP.Domain.Workflow;
using CashLineERP.SharedKernel.MasterData;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class ReferenceDataGovernanceRepository : IReferenceDataGovernanceRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public MasterDataGovernanceOverviewView GetOverview()
    {
        var entries = _dbContext.ReferenceDataEntries.AsNoTracking().ToArray();
        return new MasterDataGovernanceOverviewView(
            entries.Length,
            entries.Count(entry => entry.IsActive),
            entries.Count(entry => entry.StatusId == WorkflowStatusSeedIds.Submitted),
            entries.Count(entry => entry.ReferenceTypeCode == MasterDataReferenceTypeCode.Currency),
            entries.Count(entry => entry.ReferenceTypeCode == MasterDataReferenceTypeCode.UnitOfMeasure),
            entries.Count(entry => entry.ReferenceTypeCode == MasterDataReferenceTypeCode.TaxCode),
            entries.Count(entry => entry.ReferenceTypeCode == MasterDataReferenceTypeCode.Warehouse));
    }

    public IReadOnlyCollection<ReferenceDataEntryDetailView> GetEntries(string? referenceTypeCode = null, bool activeOnly = false)
    {
        var normalizedReferenceTypeCode = string.IsNullOrWhiteSpace(referenceTypeCode)
            ? null
            : MasterDataReferenceCatalog.NormalizeReferenceTypeCode(referenceTypeCode);

        return _dbContext.ReferenceDataEntries
            .AsNoTracking()
            .Where(entry => normalizedReferenceTypeCode == null || entry.ReferenceTypeCode == normalizedReferenceTypeCode)
            .Where(entry => !activeOnly || entry.IsActive)
            .OrderBy(entry => entry.ReferenceTypeCode)
            .ThenBy(entry => entry.ReferenceCode)
            .ToArray()
            .Select(Map)
            .ToArray();
    }

    public ReferenceDataEntryDetailView? GetEntry(long referenceDataEntryId)
    {
        var entry = _dbContext.ReferenceDataEntries
            .AsNoTracking()
            .SingleOrDefault(referenceDataEntry => referenceDataEntry.Id == referenceDataEntryId);

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

    public ReferenceDataEntryDetailView Create(CreateReferenceDataEntryCommand command)
    {
        var normalizedReferenceTypeCode = MasterDataReferenceCatalog.NormalizeReferenceTypeCode(command.ReferenceTypeCode);
        var normalizedReferenceCode = MasterDataReferenceCatalog.NormalizeReferenceCode(command.ReferenceCode);

        var nextSequence = (_dbContext.ReferenceDataEntries.Max(entry => (long?)entry.Id) ?? 0L) + 1L;
        var entry = new ReferenceDataEntry(
            entryCode: $"REF-{nextSequence:D6}",
            referenceTypeCode: normalizedReferenceTypeCode,
            referenceCode: normalizedReferenceCode,
            referenceName: command.ReferenceName,
            localizedName: command.LocalizedName,
            descriptionText: command.DescriptionText,
            attributesJson: command.AttributesJson,
            effectiveFrom: command.EffectiveFrom,
            effectiveTo: command.EffectiveTo);

        _dbContext.ReferenceDataEntries.Add(entry);
        _dbContext.SaveChanges();
        return GetEntry(entry.Id)!;
    }

    public ReferenceDataEntryDetailView Submit(long referenceDataEntryId, long actorUserId)
    {
        var entry = _dbContext.ReferenceDataEntries.Single(referenceDataEntry => referenceDataEntry.Id == referenceDataEntryId);
        entry.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "MASTER_DATA",
            documentType: "REFERENCE_DATA_ENTRY",
            documentId: entry.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Reference-data entry submitted for approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetEntry(entry.Id)!;
    }

    public ReferenceDataEntryDetailView Approve(long referenceDataEntryId, long actorUserId)
    {
        var entry = _dbContext.ReferenceDataEntries.Single(referenceDataEntry => referenceDataEntry.Id == referenceDataEntryId);
        entry.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(instance => instance.Actions)
            .SingleOrDefault(instance => instance.DocumentType == "REFERENCE_DATA_ENTRY" && instance.DocumentId == referenceDataEntryId);
        workflowInstance?.Approve(actorUserId, "Reference-data entry approved.");

        _dbContext.SaveChanges();
        return GetEntry(entry.Id)!;
    }

    public ReferenceDataEntryDetailView Activate(long referenceDataEntryId, long actorUserId)
    {
        var entry = _dbContext.ReferenceDataEntries.Single(referenceDataEntry => referenceDataEntry.Id == referenceDataEntryId);

        var conflictExists = _dbContext.ReferenceDataEntries
            .AsNoTracking()
            .Any(referenceDataEntry =>
                referenceDataEntry.Id != referenceDataEntryId
                && referenceDataEntry.IsActive
                && referenceDataEntry.ReferenceTypeCode == entry.ReferenceTypeCode
                && referenceDataEntry.ReferenceCode == entry.ReferenceCode);

        if (conflictExists)
        {
            throw new InvalidOperationException("Another active reference-data entry already exists for the same reference type and code.");
        }

        entry.Activate(actorUserId);
        _dbContext.SaveChanges();
        return GetEntry(entry.Id)!;
    }

    private static ReferenceDataEntryDetailView Map(ReferenceDataEntry entry) =>
        new(
            entry.Id,
            entry.EntryCode,
            entry.ReferenceTypeCode,
            entry.ReferenceCode,
            entry.ReferenceName,
            entry.LocalizedName,
            entry.DescriptionText,
            entry.AttributesJson,
            WorkflowStatusSeedIds.ToCode(entry.StatusId),
            entry.IsActive,
            entry.EffectiveFrom,
            entry.EffectiveTo,
            entry.SubmittedAtUtc,
            entry.SubmittedByUserId,
            entry.ApprovedAtUtc,
            entry.ApprovedByUserId,
            entry.ActivatedAtUtc,
            entry.ActivatedByUserId);
}
