using CashLineERP.Application.Documents;
using CashLineERP.Contracts.Identity;
using CashLineERP.Domain.Identity;

namespace CashLineERP.Application.Identity;

public sealed class AccountingOfficeClientRequestService : IAccountingOfficeClientRequestService
{
    private const string ModuleCode = "ACCOUNTING_OFFICE";
    private const string EntityName = "AO_CLIENT_REQUEST";

    private readonly IAccountingOfficeClientRequestRepository _repository;
    private readonly IAccountingOfficeClientAssignmentRepository _assignmentRepository;
    private readonly IDocumentStorageService _documentStorageService;

    public AccountingOfficeClientRequestService(
        IAccountingOfficeClientRequestRepository repository,
        IAccountingOfficeClientAssignmentRepository assignmentRepository,
        IDocumentStorageService documentStorageService)
    {
        _repository = repository;
        _assignmentRepository = assignmentRepository;
        _documentStorageService = documentStorageService;
    }

    public IReadOnlyCollection<AccountingOfficeClientRequestResponse> GetExternalPartyRequests(long partyId, long actorUserId) =>
        _repository.GetClientRequests(partyId).Select(MapWithAttachments).ToArray();

    public AccountingOfficeClientRequestResponse CreateExternalPartyRequest(long partyId, long actorUserId, CreateAccountingOfficeClientRequestRequest request)
    {
        var assignedOfficeUserId = _assignmentRepository.ResolvePrimaryOfficeUserId(partyId);
        var requestNumber = $"AO-{DateTime.UtcNow:yyMMddHHmmss}-{Guid.NewGuid():N}"[..22];
        var entity = new AccountingOfficeClientRequest(
            partyId,
            assignedOfficeUserId,
            requestNumber,
            request.RequestTypeCode,
            request.SubjectText,
            request.DetailsText,
            actorUserId);

        _repository.Create(entity);
        return MapWithAttachments(entity);
    }

    public IReadOnlyCollection<AccountingOfficeClientRequestResponse> GetAccountingOfficeClientRequests(long accountingOfficeUserId, long partyId)
    {
        EnsureAssigned(accountingOfficeUserId, partyId);
        return _repository.GetAssignedClientRequests(accountingOfficeUserId, partyId).Select(MapWithAttachments).ToArray();
    }

    public AccountingOfficeClientRequestResponse StartReview(long accountingOfficeUserId, long partyId, long requestId, ReviewAccountingOfficeClientRequestRequest request)
    {
        EnsureAssigned(accountingOfficeUserId, partyId);
        var entity = RequireAssignedRequest(accountingOfficeUserId, partyId, requestId);
        entity.StartReview(accountingOfficeUserId, request.ReviewNotesText);
        _repository.Update(entity);
        return MapWithAttachments(entity);
    }

    public AccountingOfficeClientRequestResponse Complete(long accountingOfficeUserId, long partyId, long requestId, CompleteAccountingOfficeClientRequestRequest request)
    {
        EnsureAssigned(accountingOfficeUserId, partyId);
        var entity = RequireAssignedRequest(accountingOfficeUserId, partyId, requestId);
        entity.Complete(accountingOfficeUserId, request.CompletionNotesText);
        _repository.Update(entity);
        return MapWithAttachments(entity);
    }

    public AccountingOfficeClientRequestResponse Reject(long accountingOfficeUserId, long partyId, long requestId, RejectAccountingOfficeClientRequestRequest request)
    {
        EnsureAssigned(accountingOfficeUserId, partyId);
        var entity = RequireAssignedRequest(accountingOfficeUserId, partyId, requestId);
        entity.Reject(accountingOfficeUserId, request.RejectionReasonText);
        _repository.Update(entity);
        return MapWithAttachments(entity);
    }

    private void EnsureAssigned(long accountingOfficeUserId, long partyId)
    {
        if (!_assignmentRepository.IsAssigned(accountingOfficeUserId, partyId))
        {
            throw new InvalidOperationException("Accounting Office user is not assigned to the requested client.");
        }
    }

    private AccountingOfficeClientRequest RequireAssignedRequest(long accountingOfficeUserId, long partyId, long requestId)
    {
        var entity = _repository.GetClientRequest(requestId)
            ?? throw new InvalidOperationException("The requested accounting office client request was not found.");

        if (entity.ClientPartyId != partyId)
        {
            throw new InvalidOperationException("The requested accounting office client request does not belong to the selected client party.");
        }

        if (entity.AssignedAccountingOfficeUserId.HasValue
            && entity.AssignedAccountingOfficeUserId.Value != accountingOfficeUserId)
        {
            throw new InvalidOperationException("The requested accounting office client request is not assigned to the signed-in accounting office user.");
        }

        return entity;
    }

    private AccountingOfficeClientRequestResponse MapWithAttachments(AccountingOfficeClientRequest entity)
    {
        var attachments = _documentStorageService.GetDocuments(
            moduleCode: ModuleCode,
            entityName: EntityName,
            entityId: entity.Id);

        return new AccountingOfficeClientRequestResponse(
            entity.Id,
            entity.ClientPartyId,
            entity.AssignedAccountingOfficeUserId,
            entity.RequestNumber,
            entity.RequestTypeCode,
            entity.SubjectText,
            entity.DetailsText,
            entity.StatusCode,
            entity.CreatedAtUtc,
            entity.CreatedByUserId,
            entity.SubmittedAtUtc,
            entity.SubmittedByUserId,
            entity.ReviewedAtUtc,
            entity.ReviewedByUserId,
            entity.CompletedAtUtc,
            entity.CompletedByUserId,
            entity.CompletionNotesText,
            entity.RejectedAtUtc,
            entity.RejectedByUserId,
            entity.RejectionReasonText,
            attachments);
    }
}
