using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using PowderCoating.Application.Configuration;
using PowderCoating.Application.Interfaces;
namespace PowderCoating.Application.Services;
///
/// Manages equipment manual documents stored in Azure Blob Storage.
/// Manuals are stored in the manuals container under the path
/// {companyId}/equipment-manuals/{equipmentId}/{sanitizedFilename}{ext}.
///
/// The 50 MB limit (5× larger than other upload types) is intentional —
/// equipment OEM manuals are often large PDF scans. Only document formats
/// are accepted; image uploads are rejected to keep this container clean.
///
///
public class EquipmentManualService : IEquipmentManualService
{
private readonly IAzureBlobStorageService _blobService;
private readonly StorageSettings _settings;
/// Maximum manual file size accepted on upload (50 MB).
private const long MaxFileSize = 50 * 1024 * 1024; // 50 MB
///
/// Document formats permitted for equipment manuals.
/// Images and spreadsheets are deliberately excluded to keep
/// the container purpose-specific.
///
private static readonly string[] AllowedExtensions = [".pdf", ".doc", ".docx", ".txt"];
///
/// Initialises the service with the blob storage provider and storage
/// configuration (container names, etc.).
///
public EquipmentManualService(
IAzureBlobStorageService blobService,
IOptions settings)
{
_blobService = blobService;
_settings = settings.Value;
}
///
/// Validates, sanitizes, and uploads an equipment manual to Azure Blob Storage.
/// The original filename (minus invalid characters) is preserved in the blob
/// name so operators can recognise the document from the path alone.
///
/// The uploaded file from the HTTP request.
/// The tenant company's database ID (for path scoping).
/// The equipment record's database ID.
///
/// A tuple with a success flag, the stored blob path (on success), and a
/// human-readable error message (on failure).
///
public async Task<(bool Success, string FilePath, string ErrorMessage)> SaveEquipmentManualAsync(IFormFile file, int companyId, int equipmentId)
{
var (isValid, extension, error) = BlobFileHelper.ValidateUpload(file, AllowedExtensions, MaxFileSize);
if (!isValid)
return (false, string.Empty, error);
// Sanitize filename — replace OS-invalid characters with underscores to
// prevent path traversal and blob naming errors in Azure.
var fileName = BlobFileHelper.SanitizeFileName(Path.GetFileNameWithoutExtension(file.FileName));
var blobName = $"{companyId}/equipment-manuals/{equipmentId}/{fileName}{extension}";
var contentType = BlobFileHelper.GetContentType(extension);
using var stream = file.OpenReadStream();
var result = await _blobService.UploadAsync(_settings.Containers.Manuals, blobName, stream, contentType);
if (!result.Success)
return (false, string.Empty, result.ErrorMessage);
return (true, blobName, string.Empty);
}
///
/// Deletes the equipment manual blob at the given path from Azure Blob Storage.
///
/// Blob-relative path previously returned by .
/// Success flag and an error message on failure.
public async Task<(bool Success, string ErrorMessage)> DeleteEquipmentManualAsync(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return (false, "File path is empty");
return await _blobService.DeleteAsync(_settings.Containers.Manuals, filePath);
}
///
/// Downloads the raw bytes of an equipment manual so the controller can
/// stream it to the browser with the appropriate content-disposition header.
///
/// Blob-relative path of the manual.
///
/// A tuple with a success flag, the raw file bytes, the MIME content type,
/// and an error message on failure.
///
public async Task<(bool Success, byte[] FileContent, string ContentType, string ErrorMessage)> GetEquipmentManualAsync(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return (false, Array.Empty(), string.Empty, "File path is empty");
return await _blobService.DownloadAsync(_settings.Containers.Manuals, filePath);
}
///
/// Checks whether a manual blob exists at the given path without downloading it.
/// Used by the Equipment Details view to determine whether a "Download Manual"
/// button should be rendered.
///
/// Blob-relative path to check.
/// true if the blob exists; otherwise false.
public async Task EquipmentManualExistsAsync(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return false;
return await _blobService.ExistsAsync(_settings.Containers.Manuals, filePath);
}
}