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

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class ForeignExchangeOverrideRepository : IForeignExchangeOverrideRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<ForeignExchangeOverrideDetailView> GetForeignExchangeOverrides(
        string? currencyCode = null,
        string? baseCurrencyCode = null,
        string? statusCode = null,
        DateOnly? effectiveDateFrom = null,
        DateOnly? effectiveDateTo = null)
    {
        if (effectiveDateFrom.HasValue && effectiveDateTo.HasValue && effectiveDateFrom.Value > effectiveDateTo.Value)
        {
            throw new InvalidOperationException("FX override effectiveDateFrom cannot be later than effectiveDateTo.");
        }

        var normalizedCurrencyCode = NormalizeOptionalCurrencyCode(currencyCode);
        var normalizedBaseCurrencyCode = NormalizeOptionalCurrencyCode(baseCurrencyCode);
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode) ? null : statusCode.Trim().ToUpperInvariant();

        return _dbContext.Set<ForeignExchangeOverride>()
            .AsNoTracking()
            .ToArray()
            .Where(entry => normalizedCurrencyCode is null || string.Equals(entry.CurrencyCode, normalizedCurrencyCode, StringComparison.OrdinalIgnoreCase))
            .Where(entry => normalizedBaseCurrencyCode is null || string.Equals(entry.BaseCurrencyCode, normalizedBaseCurrencyCode, StringComparison.OrdinalIgnoreCase))
            .Where(entry => normalizedStatusCode is null || string.Equals(WorkflowStatusSeedIds.ToCode(entry.StatusId), normalizedStatusCode, StringComparison.OrdinalIgnoreCase))
            .Where(entry => !effectiveDateFrom.HasValue || entry.EffectiveDate >= effectiveDateFrom.Value)
            .Where(entry => !effectiveDateTo.HasValue || entry.EffectiveDate <= effectiveDateTo.Value)
            .OrderByDescending(entry => entry.EffectiveDate)
            .ThenByDescending(entry => entry.Id)
            .Select(Map)
            .ToArray();
    }

    public ForeignExchangeOverrideDetailView? GetForeignExchangeOverride(long foreignExchangeOverrideId)
    {
        var overrideEntry = _dbContext.Set<ForeignExchangeOverride>()
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == foreignExchangeOverrideId);

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

    public ForeignExchangeOverrideDetailView Create(CreateForeignExchangeOverrideCommand command)
    {
        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, command.EffectiveDate, "FX override");

        var normalizedBaseCurrencyCode = NormalizeRequiredCurrencyCode(command.BaseCurrencyCode);
        var normalizedCurrencyCode = NormalizeRequiredCurrencyCode(command.CurrencyCode);
        var normalizedReasonCode = NormalizeRequiredReasonCode(command.ReasonCode);

        var duplicateExists = _dbContext.Set<ForeignExchangeOverride>()
            .AsNoTracking()
            .Any(entry =>
                entry.EffectiveDate == command.EffectiveDate
                && entry.BaseCurrencyCode == normalizedBaseCurrencyCode
                && entry.CurrencyCode == normalizedCurrencyCode);

        if (duplicateExists)
        {
            throw new InvalidOperationException(
                $"FX override already exists for {normalizedCurrencyCode}/{normalizedBaseCurrencyCode} on {command.EffectiveDate:yyyy-MM-dd}.");
        }

        var nextSequence = (_dbContext.Set<ForeignExchangeOverride>().Max(entry => (long?)entry.Id) ?? 0L) + 1L;
        var overrideEntry = new ForeignExchangeOverride(
            overrideNumber: $"FXO-{nextSequence:D6}",
            effectiveDate: command.EffectiveDate,
            baseCurrencyCode: normalizedBaseCurrencyCode,
            currencyCode: normalizedCurrencyCode,
            overrideRateToBase: command.OverrideRateToBase,
            reasonCode: normalizedReasonCode,
            notes: NormalizeOptional(command.Notes));

        _dbContext.Add(overrideEntry);
        _dbContext.SaveChanges();
        return GetForeignExchangeOverride(overrideEntry.Id)!;
    }

    public ForeignExchangeOverrideDetailView Submit(long foreignExchangeOverrideId, long actorUserId)
    {
        var overrideEntry = _dbContext.Set<ForeignExchangeOverride>().Single(entry => entry.Id == foreignExchangeOverrideId);
        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, overrideEntry.EffectiveDate, "FX override");
        overrideEntry.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "FINANCE",
            documentType: "FX_OVERRIDE",
            documentId: overrideEntry.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Manual FX override submitted for checker approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetForeignExchangeOverride(overrideEntry.Id)!;
    }

    public ForeignExchangeOverrideDetailView Approve(long foreignExchangeOverrideId, long actorUserId)
    {
        var overrideEntry = _dbContext.Set<ForeignExchangeOverride>().Single(entry => entry.Id == foreignExchangeOverrideId);
        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, overrideEntry.EffectiveDate, "FX override");
        overrideEntry.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(instance => instance.Actions)
            .SingleOrDefault(instance => instance.DocumentType == "FX_OVERRIDE" && instance.DocumentId == overrideEntry.Id);
        workflowInstance?.Approve(actorUserId, "Manual FX override approved by checker.");

        _dbContext.SaveChanges();
        return GetForeignExchangeOverride(overrideEntry.Id)!;
    }

    private static ForeignExchangeOverrideDetailView Map(ForeignExchangeOverride overrideEntry) =>
        new(
            overrideEntry.Id,
            overrideEntry.OverrideNumber,
            overrideEntry.EffectiveDate,
            overrideEntry.BaseCurrencyCode,
            overrideEntry.CurrencyCode,
            overrideEntry.OverrideRateToBase,
            overrideEntry.ReasonCode,
            overrideEntry.Notes,
            WorkflowStatusSeedIds.ToCode(overrideEntry.StatusId),
            overrideEntry.SubmittedAtUtc,
            overrideEntry.SubmittedByUserId,
            overrideEntry.ApprovedAtUtc,
            overrideEntry.ApprovedByUserId);

    private static string NormalizeRequiredCurrencyCode(string currencyCode)
    {
        var normalized = NormalizeOptionalCurrencyCode(currencyCode);
        if (normalized is null)
        {
            throw new InvalidOperationException("FX override currency code is required.");
        }

        return normalized;
    }

    private static string? NormalizeOptionalCurrencyCode(string? currencyCode) =>
        string.IsNullOrWhiteSpace(currencyCode) ? null : currencyCode.Trim().ToUpperInvariant();

    private static string NormalizeRequiredReasonCode(string reasonCode)
    {
        var normalized = reasonCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalized))
        {
            throw new InvalidOperationException("FX override reason code is required.");
        }

        return normalized;
    }

    private static string? NormalizeOptional(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
