using CashLineERP.Application.Documents;
using CashLineERP.Domain.Finance;
using CashLineERP.Application.IslamicFinance.Murabaha;
using CashLineERP.Domain.IslamicFinance.Accounting;
using CashLineERP.SharedKernel.Documents;
using CashLineERP.SharedKernel.IslamicFinance;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories.IslamicFinance;

public sealed class IslamicFinanceMurabahaAccountingHookRepository : IIslamicFinanceMurabahaAccountingHookRepository
{
    private const string MurabahaCaseSourceEntityType = "MURABAHA_CASE";
    private const long MurabahaReceivableAccountId = 1105L;
    private const long PartnerFundingPayableAccountId = 2105L;
    private const long MurabahaProfitAccountId = 4105L;

    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<IslamicFinanceAccountingHookDetailView> GetHooks(long murabahaCaseId, string? postingStatusCode = null)
    {
        EnsureMurabahaCaseExists(murabahaCaseId);

        var normalizedPostingStatusCode = string.IsNullOrWhiteSpace(postingStatusCode)
            ? null
            : IslamicFinanceWorkflowCatalog.NormalizeStatusCode(postingStatusCode);

        return _dbContext.Set<IslamicFinanceAccountingHook>()
            .AsNoTracking()
            .Where(entry => entry.SourceEntityType == MurabahaCaseSourceEntityType && entry.SourceEntityId == murabahaCaseId)
            .Where(entry => normalizedPostingStatusCode == null || entry.PostingStatusCode == normalizedPostingStatusCode)
            .OrderBy(entry => entry.Id)
            .Select(Map)
            .ToArray();
    }

    public IslamicFinanceAccountingHookDetailView? GetHook(long murabahaCaseId, long accountingHookId)
    {
        EnsureMurabahaCaseExists(murabahaCaseId);

        var entry = _dbContext.Set<IslamicFinanceAccountingHook>()
            .AsNoTracking()
            .SingleOrDefault(item =>
                item.Id == accountingHookId
                && item.SourceEntityType == MurabahaCaseSourceEntityType
                && item.SourceEntityId == murabahaCaseId);

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

    public IslamicFinanceAccountingHookDetailView RegisterHook(long murabahaCaseId, long actorUserId)
    {
        var existing = _dbContext.Set<IslamicFinanceAccountingHook>()
            .AsNoTracking()
            .SingleOrDefault(item => item.SourceEntityType == MurabahaCaseSourceEntityType && item.SourceEntityId == murabahaCaseId);

        if (existing is not null)
        {
            return Map(existing);
        }

        var murabahaCase = _dbContext.IslamicFinanceMurabahaCases
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCaseId);

        if (murabahaCase is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook requires a valid Murabaha case.");
        }

        if (!string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Active, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook registration requires an approved or active Murabaha case.");
        }

        var financingRequest = _dbContext.IslamicFinanceRequests
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCase.FinancingRequestId);

        if (financingRequest is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook requires a valid financing request.");
        }

        var product = _dbContext.IslamicFinanceProductProfiles
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCase.ProductProfileId);

        if (product is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook requires a valid product profile.");
        }

        var hasGeneratedContract = _dbContext.DocumentLinks
            .AsNoTracking()
            .Any(document =>
                document.ModuleCode == IslamicFinanceDocumentCatalog.ModuleCode
                && document.EntityName == IslamicFinanceDocumentCatalog.MurabahaCaseEntityName
                && document.EntityId == murabahaCaseId
                && document.DocumentType == IslamicFinanceDocumentCatalog.MurabahaContractDocumentType
                && document.LifecycleStatusCode == DocumentLifecycleStatusCode.Active);

        if (!hasGeneratedContract)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook registration requires an active generated Murabaha contract.");
        }

        var hasSchedule = _dbContext.IslamicFinanceProfitSchedules
            .AsNoTracking()
            .Any(entry => entry.MurabahaCaseId == murabahaCaseId);

        if (!hasSchedule)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook registration requires a generated profit schedule.");
        }

        var hook = new IslamicFinanceAccountingHook(
            MurabahaCaseSourceEntityType,
            murabahaCaseId,
            financingRequest.FundingSourceModeValue,
            product.ProductTypeValue,
            murabahaCase.CustomerPartyId,
            murabahaCase.PartnerLenderId);

        _dbContext.Add(hook);
        _dbContext.SaveChanges();
        return GetHook(murabahaCaseId, hook.Id)!;
    }

    public IslamicFinanceAccountingHookDetailView PublishHook(long murabahaCaseId, long accountingHookId, long actorUserId)
    {
        var hook = _dbContext.Set<IslamicFinanceAccountingHook>()
            .SingleOrDefault(item =>
                item.Id == accountingHookId
                && item.SourceEntityType == MurabahaCaseSourceEntityType
                && item.SourceEntityId == murabahaCaseId);

        if (hook is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting publish requires a valid accounting hook.");
        }

        if (hook.GeneralLedgerBatchId.HasValue)
        {
            return GetHook(murabahaCaseId, accountingHookId)!;
        }

        var murabahaCase = _dbContext.IslamicFinanceMurabahaCases
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == murabahaCaseId);

        if (murabahaCase is null)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting publish requires a valid Murabaha case.");
        }

        if (!string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(murabahaCase.StatusCode, IslamicFinanceWorkflowCatalog.Active, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting publish requires an approved or active Murabaha case.");
        }

        if (hook.FundingSourceModeValue != IslamicFinanceFundingSourceMode.Partner)
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting publish currently supports partner-funded Murabaha only.");
        }

        var postingDate = DateOnly.FromDateTime(murabahaCase.ActivatedAtUtc?.Date
            ?? murabahaCase.ApprovedAtUtc?.Date
            ?? DateTime.UtcNow.Date);

        var postingPeriodId = AccountingPeriodLockGuard.RequireOpenPeriod(
            _dbContext,
            postingDate,
            "Islamic Finance Murabaha accounting publish");

        using var transaction = _dbContext.Database.IsRelational()
            ? _dbContext.Database.BeginTransaction()
            : null;

        var nextBatchSequence = (_dbContext.GeneralLedgerBatches.Max(batch => (long?)batch.Id) ?? 0L) + 1L;
        var batch = new GeneralLedgerBatch(
            batchNumber: $"GLB-{nextBatchSequence:D6}",
            batchType: "IF_MURABAHA",
            branchId: null,
            periodId: postingPeriodId,
            descriptionText: $"Murabaha accounting publish for {murabahaCase.CaseNumber}",
            sourceModule: "ISLAMIC_FINANCE",
            sourceDocumentId: murabahaCaseId);

        var journal = new GeneralLedgerJournal(
            generalLedgerBatchId: batch.Id,
            journalNumber: $"JRNL-{nextBatchSequence:D6}",
            journalType: "IF_MURABAHA",
            postingDate: postingDate,
            taxDate: postingDate,
            documentDate: postingDate,
            descriptionText: $"Murabaha accounting traceability for {murabahaCase.CaseNumber}");

        journal.AddLine(
            lineNumber: 1,
            generalLedgerAccountId: MurabahaReceivableAccountId,
            branchId: null,
            departmentId: null,
            partyId: murabahaCase.CustomerPartyId,
            itemId: null,
            debitAmount: murabahaCase.SalePriceAmount,
            creditAmount: 0m,
            currencyCode: murabahaCase.TransactionCurrencyCode,
            fxRate: 1m,
            lineDescription: "Murabaha receivable recognition");

        journal.AddLine(
            lineNumber: 2,
            generalLedgerAccountId: PartnerFundingPayableAccountId,
            branchId: null,
            departmentId: null,
            partyId: murabahaCase.PartnerLenderId,
            itemId: null,
            debitAmount: 0m,
            creditAmount: murabahaCase.PurchaseCostAmount,
            currencyCode: murabahaCase.TransactionCurrencyCode,
            fxRate: 1m,
            lineDescription: "Partner funding payable recognition");

        if (murabahaCase.ProfitAmount > 0m)
        {
            journal.AddLine(
                lineNumber: 3,
                generalLedgerAccountId: MurabahaProfitAccountId,
                branchId: null,
                departmentId: null,
                partyId: murabahaCase.CustomerPartyId,
                itemId: null,
                debitAmount: 0m,
                creditAmount: murabahaCase.ProfitAmount,
                currencyCode: murabahaCase.TransactionCurrencyCode,
                fxRate: 1m,
                lineDescription: "Murabaha profit recognition");
        }

        batch.AddJournal(journal);
        _dbContext.GeneralLedgerBatches.Add(batch);
        hook.LinkGeneralLedgerBatch(batch.Id, actorUserId);
        _dbContext.SaveChanges();
        transaction?.Commit();
        return GetHook(murabahaCaseId, accountingHookId)!;
    }

    private void EnsureMurabahaCaseExists(long murabahaCaseId)
    {
        if (!_dbContext.IslamicFinanceMurabahaCases.AsNoTracking().Any(entry => entry.Id == murabahaCaseId))
        {
            throw new InvalidOperationException("Islamic Finance Murabaha accounting hook read requires a valid Murabaha case.");
        }
    }

    private static IslamicFinanceAccountingHookDetailView Map(IslamicFinanceAccountingHook entry) =>
        new(
            entry.Id,
            entry.SourceEntityType,
            entry.SourceEntityId,
            IslamicFinanceProductCatalog.NormalizeFundingSourceMode(entry.FundingSourceMode),
            IslamicFinanceProductCatalog.NormalizeProductType(entry.ProductType),
            entry.CustomerPartyId,
            entry.PartnerLenderId,
            entry.GeneralLedgerBatchId,
            IslamicFinanceWorkflowCatalog.NormalizeStatusCode(entry.PostingStatusCode),
            entry.CreatedAtUtc,
            entry.CreatedByUserId,
            entry.UpdatedAtUtc,
            entry.UpdatedByUserId);
}
