using CashLineERP.Application.IslamicFinance.Shariah;
using CashLineERP.Domain.IslamicFinance.Shariah;
using CashLineERP.SharedKernel.IslamicFinance;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories.IslamicFinance;

public sealed class ShariahReviewRepository : IShariahReviewRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<IslamicFinanceShariahReviewDetailView> GetReviewCases(
        string? statusCode = null,
        long? financingRequestId = null,
        long? productProfileId = null)
    {
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode)
            ? null
            : IslamicFinanceWorkflowCatalog.NormalizeStatusCode(statusCode);

        return _dbContext.IslamicFinanceShariahReviewCases
            .AsNoTracking()
            .Where(entry => normalizedStatusCode == null || entry.StatusCode == normalizedStatusCode)
            .Where(entry => !financingRequestId.HasValue || entry.FinancingRequestId == financingRequestId.Value)
            .Where(entry => !productProfileId.HasValue || entry.ProductProfileId == productProfileId.Value)
            .OrderBy(entry => entry.ReviewCaseNumber)
            .Select(Map)
            .ToArray();
    }

    public IslamicFinanceShariahReviewDetailView? GetReviewCase(long shariahReviewCaseId)
    {
        var entry = _dbContext.IslamicFinanceShariahReviewCases
            .AsNoTracking()
            .SingleOrDefault(reviewCase => reviewCase.Id == shariahReviewCaseId);

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

    public IslamicFinanceShariahReviewDetailView Create(CreateIslamicFinanceShariahReviewCommand command)
    {
        if (!_dbContext.IslamicFinanceProductProfiles.Any(entry => entry.Id == command.ProductProfileId))
        {
            throw new InvalidOperationException("Islamic Finance Shariah review requires a valid product profile.");
        }

        if (command.FinancingRequestId.HasValue && !_dbContext.IslamicFinanceRequests.Any(entry => entry.Id == command.FinancingRequestId.Value))
        {
            throw new InvalidOperationException("Islamic Finance Shariah review financing request reference is not valid.");
        }

        var normalizedReviewCaseNumber = command.ReviewCaseNumber.Trim().ToUpperInvariant();
        if (_dbContext.IslamicFinanceShariahReviewCases.AsNoTracking().Any(entry => entry.ReviewCaseNumber == normalizedReviewCaseNumber))
        {
            throw new InvalidOperationException("Islamic Finance Shariah review case number already exists.");
        }

        var entry = new IslamicFinanceShariahReviewCase(
            normalizedReviewCaseNumber,
            command.ProductProfileId,
            command.FinancingRequestId,
            IslamicFinanceGovernanceCatalog.ParseReasonCode(command.ReviewReasonCode));

        _dbContext.IslamicFinanceShariahReviewCases.Add(entry);
        _dbContext.SaveChanges();
        IslamicFinanceLifecycleOutboxWriter.EnqueueShariahReviewEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.ShariahReviewCreated,
            entry.CreatedByUserId);
        _dbContext.SaveChanges();
        return GetReviewCase(entry.Id)!;
    }

    public IslamicFinanceShariahReviewDetailView Submit(long shariahReviewCaseId, long actorUserId)
    {
        var entry = _dbContext.IslamicFinanceShariahReviewCases.Single(reviewCase => reviewCase.Id == shariahReviewCaseId);
        entry.Submit(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueShariahReviewEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.ShariahReviewSubmitted,
            actorUserId);
        _dbContext.SaveChanges();
        return GetReviewCase(entry.Id)!;
    }

    public IslamicFinanceShariahReviewDetailView RecommendOutcome(long shariahReviewCaseId, RecommendIslamicFinanceShariahOutcomeCommand command)
    {
        var entry = _dbContext.IslamicFinanceShariahReviewCases.Single(reviewCase => reviewCase.Id == shariahReviewCaseId);
        entry.RecommendOutcome(
            IslamicFinanceShariahOutcomeCatalog.ParseOutcomeCode(command.ReviewOutcomeCode),
            command.ReviewNotes,
            command.ReviewerUserId);

        IslamicFinanceLifecycleOutboxWriter.EnqueueShariahReviewEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.ShariahReviewRecommended,
            command.ReviewerUserId);
        _dbContext.SaveChanges();
        return GetReviewCase(entry.Id)!;
    }

    public IslamicFinanceShariahReviewDetailView ApproveOutcome(long shariahReviewCaseId, long actorUserId)
    {
        var entry = _dbContext.IslamicFinanceShariahReviewCases.Single(reviewCase => reviewCase.Id == shariahReviewCaseId);
        entry.ApproveOutcome(actorUserId);
        IslamicFinanceLifecycleOutboxWriter.EnqueueShariahReviewEvent(
            _dbContext,
            entry,
            IslamicFinanceIntegrationEventCatalog.ShariahReviewDecided,
            actorUserId);
        _dbContext.SaveChanges();
        return GetReviewCase(entry.Id)!;
    }

    private static IslamicFinanceShariahReviewDetailView Map(IslamicFinanceShariahReviewCase entry) =>
        new(
            entry.Id,
            entry.ReviewCaseNumber,
            entry.ProductProfileId,
            entry.FinancingRequestId,
            IslamicFinanceGovernanceCatalog.NormalizeReasonCode(entry.ReviewReasonCode),
            entry.ReviewOutcomeCode is null ? null : IslamicFinanceShariahOutcomeCatalog.NormalizeOutcomeCode(entry.ReviewOutcomeCode),
            entry.ReviewNotes,
            IslamicFinanceWorkflowCatalog.NormalizeStatusCode(entry.StatusCode),
            entry.WorkflowInstanceId,
            entry.SubmittedAtUtc,
            entry.SubmittedByUserId,
            entry.ReviewedAtUtc,
            entry.ReviewedByUserId,
            entry.ApprovedAtUtc,
            entry.ApprovedByUserId);
}
