using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;

namespace OmsorgCore.Application.Services;

public class DocumentService : IDocumentService
{
    private const string DocumentCategoryListKey = "DocumentCategory";

    private readonly IDocumentRepository _documentRepository;
    private readonly IDocumentStorage _documentStorage;
    private readonly IDocumentUploadPolicy _uploadPolicy;
    private readonly IValueListRepository _valueListRepository;

    public DocumentService(
        IDocumentRepository documentRepository,
        IDocumentStorage documentStorage,
        IDocumentUploadPolicy uploadPolicy,
        IValueListRepository valueListRepository)
    {
        _documentRepository = documentRepository;
        _documentStorage = documentStorage;
        _uploadPolicy = uploadPolicy;
        _valueListRepository = valueListRepository;
    }

    public async Task<DocumentUploadResult> UploadAsync(
        string entityType,
        Guid entityId,
        string category,
        string? description,
        string fileName,
        string contentType,
        long sizeBytes,
        Stream content,
        Guid uploadedByUserId,
        CancellationToken cancellationToken = default)
    {
        if (!_uploadPolicy.IsSizeAllowed(sizeBytes))
        {
            return new DocumentUploadResult(null, DocumentUploadError.FileTooLarge);
        }

        if (!_uploadPolicy.IsContentTypeAllowed(contentType))
        {
            return new DocumentUploadResult(null, DocumentUploadError.ContentTypeNotAllowed);
        }

        var allowedCategories = await _valueListRepository.GetActiveValuesAsync(DocumentCategoryListKey, cancellationToken);
        if (!allowedCategories.Contains(category))
        {
            return new DocumentUploadResult(null, DocumentUploadError.InvalidCategory);
        }

        var document = new Document
        {
            EntityType = entityType,
            EntityId = entityId,
            Category = category,
            Description = description,
            FileName = fileName,
            ContentType = contentType,
            SizeBytes = sizeBytes,
            UploadedByUserId = uploadedByUserId
        };

        document.StorageKey = await _documentStorage.SaveAsync(entityType, entityId, document.Id, fileName, content, cancellationToken);

        await _documentRepository.AddAsync(document, cancellationToken);
        await _documentRepository.SaveChangesAsync(cancellationToken);

        return new DocumentUploadResult(document, DocumentUploadError.None);
    }

    public async Task<DocumentUpdateResult> UpdateAsync(
        Guid id,
        string category,
        string? description,
        string fileName,
        CancellationToken cancellationToken = default)
    {
        var document = await _documentRepository.GetByIdAsync(id, cancellationToken);
        if (document is null)
        {
            return new DocumentUpdateResult(null, DocumentUpdateError.NotFound);
        }

        var allowedCategories = await _valueListRepository.GetActiveValuesAsync(DocumentCategoryListKey, cancellationToken);
        if (!allowedCategories.Contains(category))
        {
            return new DocumentUpdateResult(null, DocumentUpdateError.InvalidCategory);
        }

        document.Category = category;
        document.Description = description;
        document.FileName = fileName;
        document.UpdatedAt = DateTime.UtcNow;

        await _documentRepository.SaveChangesAsync(cancellationToken);

        return new DocumentUpdateResult(document, DocumentUpdateError.None);
    }

    public Task<IReadOnlyList<Document>> GetByEntityAsync(string entityType, Guid entityId, CancellationToken cancellationToken = default)
        => _documentRepository.GetByEntityAsync(entityType, entityId, cancellationToken);

    public Task<Document?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
        => _documentRepository.GetByIdAsync(id, cancellationToken);

    public async Task<Stream?> OpenForDownloadAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var document = await _documentRepository.GetByIdAsync(id, cancellationToken);
        if (document is null)
        {
            return null;
        }

        return await _documentStorage.OpenReadAsync(document.StorageKey, cancellationToken);
    }

    public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var deleted = await _documentRepository.SoftDeleteAsync(id, cancellationToken);
        if (deleted)
        {
            await _documentRepository.SaveChangesAsync(cancellationToken);
        }

        return deleted;
    }

    public Task<IReadOnlyList<Document>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
        => _documentRepository.GetDeletedAsync(search, cancellationToken);

    public async Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var restored = await _documentRepository.RestoreAsync(id, cancellationToken);
        if (restored)
        {
            await _documentRepository.SaveChangesAsync(cancellationToken);
        }

        return restored;
    }
}
