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

namespace PowderCoating.Application.Services;

/// <summary>
/// Manages the two-phase lifecycle of AI photo quote images in Azure Blob Storage.
/// Phase 1 (temp): photos uploaded during the quote wizard are stored under
/// <c>temp/{tempId}/{guid}{ext}</c> before the quote is saved and has an ID.
/// Phase 2 (permanent): when the quote is created or edited, temp blobs are promoted to
/// <c>{companyId}/quote-photos/{quoteId}/{guid}{ext}</c> and the temp blobs are deleted.
/// Keeping photos in temp storage until the quote is committed prevents orphaned blobs from
/// quotes that are abandoned mid-wizard, and ensures blobs are never written to a quote-scoped
/// path before a valid quote ID exists.
/// </summary>
public class QuotePhotoService : IQuotePhotoService
{
    private readonly IAzureBlobStorageService _blobService;
    private readonly StorageSettings _settings;
    private readonly ILogger<QuotePhotoService> _logger;

    private static readonly string[] AllowedExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
    private const long MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB

    /// <summary>
    /// Initializes the service with the blob storage wrapper, storage configuration, and logger.
    /// </summary>
    public QuotePhotoService(
        IAzureBlobStorageService blobService,
        IOptions<StorageSettings> settings,
        ILogger<QuotePhotoService> logger)
    {
        _blobService = blobService;
        _settings = settings.Value;
        _logger = logger;
    }

    // Temp blobs:      temp/{tempId}/{guid}{ext}
    // Permanent blobs: {companyId}/quote-photos/{quoteId}/{guid}{ext}

    /// <summary>
    /// Validates and uploads a photo to temporary blob storage, generating a new <c>tempId</c>
    /// (GUID) that the client uses to reference this photo during the rest of the quote wizard.
    /// A fresh GUID blob name is used within the temp folder so that concurrent uploads for the
    /// same session do not overwrite each other.
    /// </summary>
    public async Task<(bool Success, string TempId, string FilePath, string ErrorMessage)> SaveTempPhotoAsync(
        IFormFile file, int companyId)
    {
        var (isValid, ext, validationError) = BlobFileHelper.ValidateUpload(file, AllowedExtensions, MaxFileSizeBytes);
        if (!isValid)
            return (false, string.Empty, string.Empty, validationError);

        var tempId = Guid.NewGuid().ToString("N");
        var blobName = $"temp/{tempId}/{Guid.NewGuid():N}{ext}";
        var contentType = BlobFileHelper.GetContentType(ext);

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

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

        _logger.LogInformation("Saved temp quote photo {TempId}: {BlobName}", tempId, blobName);
        return (true, tempId, blobName, string.Empty);
    }

    /// <summary>
    /// Promotes a previously uploaded temp photo to its permanent blob path once the quote has been
    /// saved and its ID is known. The photo is downloaded from temp storage and re-uploaded to the
    /// permanent path rather than using a server-side copy API, which keeps the service compatible
    /// with blob storage providers that do not support atomic renames. The temp blob is deleted
    /// on a best-effort basis after a successful promotion.
    /// </summary>
    public async Task<(bool Success, string FilePath, string ErrorMessage)> PromoteTempPhotoAsync(
        string tempId, int quoteId, int companyId)
    {
        var prefix = $"temp/{tempId}/";
        var blobs = (await _blobService.ListBlobsByPrefixAsync(_settings.Containers.QuoteImages, prefix)).ToList();

        if (blobs.Count == 0)
            return (false, string.Empty, "Temp photo not found.");

        var srcBlob = blobs[0];
        var ext = Path.GetExtension(srcBlob);
        var destBlob = $"{companyId}/quote-photos/{quoteId}/{Guid.NewGuid():N}{ext}";

        // Download from temp then re-upload to permanent location
        var download = await _blobService.DownloadAsync(_settings.Containers.QuoteImages, srcBlob);
        if (!download.Success)
            return (false, string.Empty, "Failed to read temp photo.");

        using var ms = new MemoryStream(download.Content);
        var upload = await _blobService.UploadAsync(_settings.Containers.QuoteImages, destBlob, ms, BlobFileHelper.GetContentType(ext));
        if (!upload.Success)
            return (false, string.Empty, "Failed to save permanent photo.");

        // Best-effort cleanup of temp blob
        await _blobService.DeleteAsync(_settings.Containers.QuoteImages, srcBlob);

        _logger.LogInformation("Promoted temp photo {TempId} to {DestBlob}", tempId, destBlob);
        return (true, destBlob, string.Empty);
    }

    /// <summary>
    /// Downloads a photo from blob storage and returns its raw bytes and content type.
    /// Used by the <c>Quotes/Photo</c> controller action which streams photos stored outside
    /// the public web root so they are not directly URL-accessible.
    /// </summary>
    public async Task<(bool Success, byte[] Data, string ContentType)> ReadPhotoAsync(string filePath)
    {
        var result = await _blobService.DownloadAsync(_settings.Containers.QuoteImages, filePath);
        if (!result.Success)
            return (false, Array.Empty<byte>(), string.Empty);

        return (true, result.Content, result.ContentType);
    }

    /// <summary>
    /// Deletes a photo blob from storage. Returns <c>true</c> if deletion succeeded (including the
    /// case where the blob does not exist, which the underlying service treats as a no-op success).
    /// </summary>
    public async Task<bool> DeletePhotoAsync(string filePath)
    {
        var result = await _blobService.DeleteAsync(_settings.Containers.QuoteImages, filePath);
        return result.Success;
    }

    /// <summary>
    /// Downloads all blobs under the temp prefix for a given <paramref name="tempId"/> and returns
    /// them as in-memory byte arrays. Used by the AI analysis endpoint to supply photo bytes to the
    /// Anthropic API when the quote has not yet been saved and photos have no permanent path.
    /// </summary>
    public async Task<List<(byte[] Data, string ContentType, string FileName)>> ReadTempPhotosAsync(string tempId)
    {
        var prefix = $"temp/{tempId}/";
        var blobs = (await _blobService.ListBlobsByPrefixAsync(_settings.Containers.QuoteImages, prefix)).ToList();

        var results = new List<(byte[] Data, string ContentType, string FileName)>();
        foreach (var blobName in blobs)
        {
            var download = await _blobService.DownloadAsync(_settings.Containers.QuoteImages, blobName);
            if (download.Success)
                results.Add((download.Content, download.ContentType, Path.GetFileName(blobName)));
        }

        return results;
    }

    /// <summary>
    /// Deletes all blobs under the temp prefix for a given <paramref name="tempId"/>.
    /// Called when the user cancels the quote wizard or when promotion fails partway through so
    /// that orphaned temp blobs do not accumulate in storage. Each blob deletion is wrapped in
    /// its own try/catch so that one failed delete does not abort cleanup of the remaining blobs.
    /// </summary>
    public async Task CleanupTempAsync(string tempId)
    {
        var prefix = $"temp/{tempId}/";
        var blobs = await _blobService.ListBlobsByPrefixAsync(_settings.Containers.QuoteImages, prefix);
        foreach (var blob in blobs)
        {
            try { await _blobService.DeleteAsync(_settings.Containers.QuoteImages, blob); }
            catch (Exception ex) { _logger.LogWarning(ex, "Could not clean up temp blob {Blob}", blob); }
        }
    }

}
