using CashLineERP.Application.Documents;
using CashLineERP.Domain.Documents;
using CashLineERP.SharedKernel.Documents;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class DocumentStorageRepository : IDocumentStorageRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<DocumentAttachmentDetailView> GetDocuments(
        string? moduleCode = null,
        string? entityName = null,
        long? entityId = null,
        string? lifecycleStatusCode = null,
        string? attachmentCategoryCode = null)
    {
        var query = _dbContext.DocumentLinks.AsNoTracking();

        if (!string.IsNullOrWhiteSpace(moduleCode))
        {
            var normalizedModuleCode = moduleCode.Trim().ToUpperInvariant();
            query = query.Where(document => document.ModuleCode == normalizedModuleCode);
        }

        if (!string.IsNullOrWhiteSpace(entityName))
        {
            var normalizedEntityName = entityName.Trim().ToUpperInvariant();
            query = query.Where(document => document.EntityName == normalizedEntityName);
        }

        if (entityId.HasValue)
        {
            query = query.Where(document => document.EntityId == entityId.Value);
        }

        if (!string.IsNullOrWhiteSpace(lifecycleStatusCode))
        {
            var normalizedLifecycleStatusCode = lifecycleStatusCode.Trim().ToUpperInvariant();
            query = query.Where(document => document.LifecycleStatusCode == normalizedLifecycleStatusCode);
        }

        if (!string.IsNullOrWhiteSpace(attachmentCategoryCode))
        {
            var normalizedAttachmentCategoryCode = attachmentCategoryCode.Trim().ToUpperInvariant();
            query = query.Where(document => document.AttachmentCategoryCode == normalizedAttachmentCategoryCode);
        }

        return query
            .OrderByDescending(document => document.UploadedAtUtc)
            .ThenByDescending(document => document.Id)
            .ToArray()
            .Select(Map)
            .ToArray();
    }

    public DocumentAttachmentDetailView? GetDocument(long documentLinkId)
    {
        var document = _dbContext.DocumentLinks
            .AsNoTracking()
            .SingleOrDefault(documentLink => documentLink.Id == documentLinkId);

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

    public DocumentAttachmentDetailView CreateDocument(long actorUserId, CreateDocumentAttachmentCommand command)
    {
        EnsureNoDuplicateActiveStorageReference(
            command.ModuleCode,
            command.EntityName,
            command.EntityId,
            command.DocumentType,
            command.StorageReference);

        var document = new DocumentLink(
            moduleCode: command.ModuleCode.Trim().ToUpperInvariant(),
            entityName: command.EntityName.Trim().ToUpperInvariant(),
            entityId: command.EntityId,
            documentType: command.DocumentType.Trim().ToUpperInvariant(),
            attachmentCategoryCode: command.AttachmentCategoryCode.Trim().ToUpperInvariant(),
            fileName: command.FileName.Trim(),
            storageReference: command.StorageReference.Trim(),
            storageProviderCode: command.StorageProviderCode.Trim().ToUpperInvariant(),
            contentType: string.IsNullOrWhiteSpace(command.ContentType) ? null : command.ContentType.Trim(),
            fileSizeBytes: command.FileSizeBytes,
            checksumText: string.IsNullOrWhiteSpace(command.ChecksumText) ? null : command.ChecksumText.Trim(),
            retentionUntilUtc: command.RetentionUntilUtc,
            versionNumber: 1,
            uploadedByUserId: actorUserId);

        _dbContext.DocumentLinks.Add(document);
        _dbContext.SaveChanges();

        return GetDocument(document.Id)!;
    }

    public DocumentAttachmentDetailView ArchiveDocument(long documentLinkId, long actorUserId, string? reasonText)
    {
        var document = _dbContext.DocumentLinks.SingleOrDefault(documentLink => documentLink.Id == documentLinkId);
        if (document is null)
        {
            throw new InvalidOperationException($"Document link '{documentLinkId}' was not found.");
        }

        document.Archive(actorUserId, reasonText);
        _dbContext.SaveChanges();
        return GetDocument(document.Id)!;
    }

    public DocumentAttachmentDetailView SupersedeDocument(long documentLinkId, long actorUserId, SupersedeDocumentAttachmentCommand command)
    {
        var existingDocument = _dbContext.DocumentLinks.SingleOrDefault(documentLink => documentLink.Id == documentLinkId);
        if (existingDocument is null)
        {
            throw new InvalidOperationException($"Document link '{documentLinkId}' was not found.");
        }

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

        EnsureNoDuplicateActiveStorageReference(
            existingDocument.ModuleCode,
            existingDocument.EntityName,
            existingDocument.EntityId,
            existingDocument.DocumentType,
            command.StorageReference,
            existingDocument.Id);

        var replacementDocument = new DocumentLink(
            moduleCode: existingDocument.ModuleCode,
            entityName: existingDocument.EntityName,
            entityId: existingDocument.EntityId,
            documentType: existingDocument.DocumentType,
            attachmentCategoryCode: existingDocument.AttachmentCategoryCode,
            fileName: command.FileName.Trim(),
            storageReference: command.StorageReference.Trim(),
            storageProviderCode: command.StorageProviderCode.Trim().ToUpperInvariant(),
            contentType: string.IsNullOrWhiteSpace(command.ContentType) ? null : command.ContentType.Trim(),
            fileSizeBytes: command.FileSizeBytes,
            checksumText: string.IsNullOrWhiteSpace(command.ChecksumText) ? null : command.ChecksumText.Trim(),
            retentionUntilUtc: command.RetentionUntilUtc,
            versionNumber: existingDocument.VersionNumber + 1,
            uploadedByUserId: actorUserId);

        _dbContext.DocumentLinks.Add(replacementDocument);
        _dbContext.SaveChanges();

        existingDocument.MarkSuperseded(actorUserId, replacementDocument.Id, command.ReasonText);
        _dbContext.SaveChanges();
        transaction?.Commit();

        return GetDocument(replacementDocument.Id)!;
    }

    private void EnsureNoDuplicateActiveStorageReference(
        string moduleCode,
        string entityName,
        long entityId,
        string documentType,
        string storageReference,
        long? excludedDocumentLinkId = null)
    {
        var normalizedModuleCode = moduleCode.Trim().ToUpperInvariant();
        var normalizedEntityName = entityName.Trim().ToUpperInvariant();
        var normalizedDocumentType = documentType.Trim().ToUpperInvariant();
        var normalizedStorageReference = storageReference.Trim();

        var query = _dbContext.DocumentLinks.Where(document =>
            document.ModuleCode == normalizedModuleCode &&
            document.EntityName == normalizedEntityName &&
            document.EntityId == entityId &&
            document.DocumentType == normalizedDocumentType &&
            document.StorageReference == normalizedStorageReference &&
            document.LifecycleStatusCode == DocumentLifecycleStatusCode.Active);

        if (excludedDocumentLinkId.HasValue)
        {
            query = query.Where(document => document.Id != excludedDocumentLinkId.Value);
        }

        if (query.Any())
        {
            throw new InvalidOperationException("An active document attachment already uses the same storage reference for this entity and document type.");
        }
    }

    private static DocumentAttachmentDetailView Map(DocumentLink document) =>
        new(
            document.Id,
            document.ModuleCode,
            document.EntityName,
            document.EntityId,
            document.DocumentType,
            document.AttachmentCategoryCode,
            document.FileName,
            document.StorageReference,
            document.StorageProviderCode,
            document.ContentType,
            document.FileSizeBytes,
            document.ChecksumText,
            document.LifecycleStatusCode,
            document.VersionNumber,
            document.SupersededByDocumentLinkId,
            document.RetentionUntilUtc,
            document.ArchivedAtUtc,
            document.ArchivedByUserId,
            document.LifecycleNotesText,
            document.UploadedAtUtc,
            document.UploadedByUserId);
}
