using DataAccess.Data;
using DataAccess.Models;
using System.Diagnostics;
using SixLabors.ImageSharp;

namespace KIKunstKirstenKlöckner.Data;

public class ImageGenerator
{
    private readonly HttpClient _client = new();
    private readonly BildInfoData _bildInfoData;

    private readonly IConfiguration _config;

    //private string ModelName => _config.GetValue<string>("StableDiffusionModel") ?? "No Model Defined";

    public ImageGenerator(IConfiguration config, BildInfoData bildInfoData)
    {
        _config = config;
        _bildInfoData = bildInfoData;

        string? inferenceApiKey = config.GetValue<string>("API:HF_Inference");

        _client.DefaultRequestHeaders.Clear();
        _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {inferenceApiKey}");
    }
    
    /// <summary>
    /// Geneiert ein Bild für den gegebenen Wunsch.
    /// </summary>
    /// <param name="imagePrompt">Der Prompt für den das Bild erzeugt wird.</param>
    /// <param name="wunschInfo">Der Wunsch zu dem ein Bild generiert werden soll.</param>
    /// <param name="width">Die breite des zu generierenden Bildes. <see langword="null"/> für Standardbreite des Modells</param>
    /// <param name="height">Die höhe des zu generierenden Bildes. <see langword="null"/> für Standardhöhe des Modells</param>
    /// <param name="negativePromt">Begriffe, die explizit nicht generiert werden sollen.</param>
    /// <returns>Die BildInfo des generierten Bildes; oder null, wenn ein Fehler auftrat.</returns>
    public async Task<BildInfoModel?> GenerateImageAsync(string imagePrompt, string modelName, WunschInfoModel wunschInfo, int? width = null, int? height = null, string negativePromt = "")
    {
        var postData = new
        {
            inputs = imagePrompt,
            parameters = new 
            {
                negative_prompt = negativePromt, //"photorealistic, highly detailed, 8K, portrait",
                width = width,
                height = height
            },
            options = new
            {
                // https://huggingface.co/docs/api-inference/detailed_parameters
                // Cache deaktivieren, damit Huggingface für den selben Prompt unterschiedliche Ergebnisse liefert
                use_cache = false,
                // Ignoriert 503-Fehler. Diese treten auf wenn Hugging Face das Model noch nicht geladen hat.
                // Mit true ignorieren wir den Fehler und schlagen erst fehl, wenn wir einen timeout haben.
                wait_for_model = true
            }
        };

        JsonContent content = JsonContent.Create(postData);

        try
        {
            var inferenceModelUrl = $"https://api-inference.huggingface.co/models/{modelName}";

            var response = await _client.PostAsync(inferenceModelUrl, content);

            if (response?.IsSuccessStatusCode == true)
            {
                await using Stream imageStream = await response.Content.ReadAsStreamAsync();

                DateTime imageDate = DateTime.Now;
                
                BildInfoModel bildInfo = new()
                {
                    // Die tatsächliche Url wird in SaveImageStreamAsync gesetzt.
                    Dateiname = "PlaceHolder",
                    Datum = imageDate,
                    ImageModel = modelName,
                    WunschId = wunschInfo.Id,
                    Prompt = imagePrompt
                };

                await SaveImageStreamAsync(imageStream, bildInfo);

                return bildInfo;
            }
            else
            {
                Console.WriteLine($"Image conversion failed: {response}");

                if (Debugger.IsAttached)
                    Debugger.Break();

                return null;
            }
        }
        catch (Exception exception)
        {
            Console.WriteLine($"Image request failed: {exception}");

            if (Debugger.IsAttached)
                Debugger.Break();

            return null;
        }
    }
    
    /// <summary>
    /// Speichert den Inhalt des gegebenen Datenstroms als Bild ab.
    /// </summary>
    /// <param name="dataStream">Der Datenstrom, der das Bild enthält.</param>
    /// <param name="bildInfo">Die BildInfo des Bildes. Der Dateiname wird nach dem Speichern aktualisiert.</param>
    private async Task SaveImageStreamAsync(Stream dataStream, BildInfoModel bildInfo)
    {
        using Image image = await Image.LoadAsync(dataStream);

        // Speichert BildInfo in der Datenbank, aktualisiert Id der bildInfo
        await _bildInfoData.InsertBildInfoAsync(bildInfo);

        string imageUrl = $"generated_images/Image_{bildInfo.Id}.jpg";

        string imageFilePath = $"./wwwroot/{imageUrl}";
        await image.SaveAsJpegAsync(imageFilePath);
        
        // Speichert den geänderten Dateinamen in der Datenbank
        bildInfo.Dateiname = imageUrl;
        await _bildInfoData.UpdateBildInfoDateinameAsync(bildInfo);
    }
}
