using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.MasterData;
using CashLineERP.SharedKernel.Workflow;

namespace CashLineERP.Domain.MasterData;

public sealed class ReferenceDataEntry : Entity
{
    public long Id { get; private set; }
    public string EntryCode { get; private set; } = string.Empty;
    public string ReferenceTypeCode { get; private set; } = string.Empty;
    public string ReferenceCode { get; private set; } = string.Empty;
    public string ReferenceName { get; private set; } = string.Empty;
    public string? LocalizedName { get; private set; }
    public string? DescriptionText { get; private set; }
    public string? AttributesJson { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    public bool IsActive { get; private set; }
    public DateOnly EffectiveFrom { get; private set; }
    public DateOnly? EffectiveTo { get; private set; }
    public DateTime? SubmittedAtUtc { get; private set; }
    public long? SubmittedByUserId { get; private set; }
    public DateTime? ApprovedAtUtc { get; private set; }
    public long? ApprovedByUserId { get; private set; }
    public DateTime? ActivatedAtUtc { get; private set; }
    public long? ActivatedByUserId { get; private set; }

    public ReferenceDataEntry(
        string entryCode,
        string referenceTypeCode,
        string referenceCode,
        string referenceName,
        string? localizedName,
        string? descriptionText,
        string? attributesJson,
        DateOnly effectiveFrom,
        DateOnly? effectiveTo)
    {
        EntryCode = MasterDataReferenceCatalog.NormalizeReferenceCode(entryCode);
        ReferenceTypeCode = MasterDataReferenceCatalog.NormalizeReferenceTypeCode(referenceTypeCode);
        ReferenceCode = MasterDataReferenceCatalog.NormalizeReferenceCode(referenceCode);
        ReferenceName = MasterDataReferenceCatalog.NormalizeReferenceName(referenceName);
        LocalizedName = MasterDataReferenceCatalog.NormalizeLocalizedName(localizedName);
        DescriptionText = MasterDataReferenceCatalog.NormalizeDescription(descriptionText);
        AttributesJson = MasterDataReferenceCatalog.NormalizeAttributesJson(attributesJson);
        EffectiveFrom = effectiveFrom;
        EffectiveTo = effectiveTo;

        if (effectiveTo.HasValue && effectiveTo.Value < effectiveFrom)
        {
            throw new InvalidOperationException("Reference effectiveTo cannot be earlier than effectiveFrom.");
        }
    }

    private ReferenceDataEntry()
    {
    }

    public void Submit(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft && StatusId != WorkflowStatusSeedIds.Returned)
        {
            throw new InvalidOperationException("Only draft or returned reference-data entries can be submitted.");
        }

        StatusId = WorkflowStatusSeedIds.Submitted;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        ApprovedAtUtc = null;
        ApprovedByUserId = null;
        Touch(actorUserId);
    }

    public void Approve(long actorUserId)
    {
        if (StatusId == WorkflowStatusSeedIds.Approved)
        {
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted reference-data entries can be approved.");
        }

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Reference-data maker-checker control prohibits self-approval.");
        }

        StatusId = WorkflowStatusSeedIds.Approved;
        ApprovedAtUtc = DateTime.UtcNow;
        ApprovedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Activate(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Approved)
        {
            throw new InvalidOperationException("Only approved reference-data entries can be activated.");
        }

        IsActive = true;
        ActivatedAtUtc = DateTime.UtcNow;
        ActivatedByUserId = actorUserId;
        Touch(actorUserId);
    }
}
