using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PowderCoating.Application.Configuration;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Enums;

namespace PowderCoating.Application.Services;

/// <summary>
/// Manages job progress/completion photos stored in Azure Blob Storage.
/// Photos are stored in the <c>jobimages</c> container under the path
/// <c>{companyId}/job-photos/{jobId}/{guid}{ext}</c>.
/// <para>
/// A random GUID is used in the blob name (instead of a sequential ID or the
/// original filename) to prevent direct enumeration of photos across jobs by
/// guessing predictable URLs — a common IDOR (Insecure Direct Object Reference)
/// vulnerability pattern.
/// </para>
/// </summary>
public class JobPhotoService : IJobPhotoService
{
    private readonly IAzureBlobStorageService _blobService;
    private readonly StorageSettings _settings;
    private readonly ILogger<JobPhotoService> _logger;

    /// <summary>Image extensions accepted for job photos.</summary>
    private static readonly string[] AllowedImageTypes = [".jpg", ".jpeg", ".png", ".gif", ".webp"];

    /// <summary>Maximum photo size accepted on upload (10 MB).</summary>
    private const long MaxPhotoSize = 10 * 1024 * 1024; // 10 MB

    /// <summary>
    /// Initialises the service with the blob storage provider, storage
    /// configuration, and a logger for upload audit messages.
    /// </summary>
    public JobPhotoService(
        IAzureBlobStorageService blobService,
        IOptions<StorageSettings> settings,
        ILogger<JobPhotoService> logger)
    {
        _blobService = blobService;
        _settings = settings.Value;
        _logger = logger;
    }

    /// <summary>
    /// Validates and uploads a job photo to Azure Blob Storage.
    /// A GUID is generated for the blob name to prevent IDOR enumeration attacks —
    /// without it, an attacker could iterate <c>/job-photos/42/1.jpg</c>,
    /// <c>/job-photos/42/2.jpg</c>, etc. to access another company's photos.
    /// The <paramref name="caption"/> and <paramref name="photoType"/> parameters
    /// are accepted for API symmetry but are persisted by the caller (controller)
    /// in the <c>JobPhoto</c> entity, not by this service.
    /// </summary>
    /// <param name="file">The uploaded image file from the HTTP request.</param>
    /// <param name="jobId">The job record's database ID.</param>
    /// <param name="companyId">The tenant company's database ID (for path scoping).</param>
    /// <param name="caption">Optional display caption stored by the caller.</param>
    /// <param name="photoType">Photo classification (Before, After, Progress, etc.).</param>
    /// <returns>
    /// A tuple with a success flag, the stored blob path (on success), and a
    /// human-readable error message (on failure).
    /// </returns>
    public async Task<(bool Success, string FilePath, string ErrorMessage)> SaveJobPhotoAsync(
        IFormFile file,
        int jobId,
        int companyId,
        string? caption = null,
        JobPhotoType photoType = JobPhotoType.Progress)
    {
        var (isValid, extension, error) = BlobFileHelper.ValidateUpload(file, AllowedImageTypes, MaxPhotoSize);
        if (!isValid)
            return (false, string.Empty, error);

        // SECURITY: Use GUID for blob name to prevent enumeration
        var blobName = $"{companyId}/job-photos/{jobId}/{Guid.NewGuid()}{extension}";
        var contentType = BlobFileHelper.GetContentType(extension);

        using var stream = file.OpenReadStream();
        var result = await _blobService.UploadAsync(_settings.Containers.JobImages, blobName, stream, contentType);

        if (!result.Success)
            return (false, string.Empty, result.ErrorMessage);

        _logger.LogInformation("Job photo saved: {BlobName} for job {JobId}", blobName, jobId);
        return (true, blobName, string.Empty);
    }

    /// <summary>
    /// Deletes the job photo blob at the given path from Azure Blob Storage.
    /// Called when a user removes a photo from the Job Details view.
    /// </summary>
    /// <param name="filePath">Blob-relative path previously returned by <see cref="SaveJobPhotoAsync"/>.</param>
    /// <returns>Success flag and an error message on failure.</returns>
    public async Task<(bool Success, string ErrorMessage)> DeleteJobPhotoAsync(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            return (false, "File path is required.");

        return await _blobService.DeleteAsync(_settings.Containers.JobImages, filePath);
    }

    /// <summary>
    /// Downloads the raw bytes of a job photo for serving through the controller's
    /// photo-proxy endpoint (which enforces tenant authorization before streaming).
    /// </summary>
    /// <param name="filePath">Blob-relative path of the photo.</param>
    /// <returns>
    /// A tuple with a success flag, the raw file bytes, the MIME content type,
    /// and an error message on failure.
    /// </returns>
    public async Task<(bool Success, byte[] FileContent, string ContentType, string ErrorMessage)> GetJobPhotoAsync(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            return (false, Array.Empty<byte>(), string.Empty, "File path is required.");

        return await _blobService.DownloadAsync(_settings.Containers.JobImages, filePath);
    }

    /// <summary>
    /// Checks whether a photo blob exists without downloading its content.
    /// </summary>
    /// <param name="filePath">Blob-relative path to check.</param>
    /// <returns><c>true</c> if the blob exists; otherwise <c>false</c>.</returns>
    public async Task<bool> JobPhotoExistsAsync(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            return false;

        return await _blobService.ExistsAsync(_settings.Containers.JobImages, filePath);
    }

}
