using System.Collections.Immutable;
using Exsersewo.Common.Extensions;
using Exsersewo.Common.Utilities;
using ImageMagick;
using Kynareth.Models;
using Kynareth.Extensions;

namespace Kynareth.Managers;

public class ImageGenerator : ImageModifier
{
	private static List<ImageGenerationEndpoint> GenerationEndpoints;
	private static IReadOnlyList<string> sourceFiles;

	private static string GenerationTemplateBaseFolder;
	
	public ImageGenerator(IConfiguration configuration, IWebHostEnvironment appEnvironment)
	{
		sourceFiles = Directory.EnumerateFiles(Path.Combine(appEnvironment.WebRootPath, configuration.GetValue<string>("ShitpostBot:Folder"), "..", "random")).ToImmutableList();
		GenerationTemplateBaseFolder = Path.Combine(appEnvironment.WebRootPath, configuration.GetValue<string>("ShitpostBot:Folder"));
		GenerationEndpoints = configuration.GetSection("ShitpostBot:Templates").Get<List<ImageGenerationEndpoint>>();
	}
	
	public async Task<Object> GenerateImageAsync(CancellationToken cancellationToken)
	{
		ImageGenerationImage imageTemplate = null;

		var endpoint = GenerationEndpoints.Random();

		if (endpoint is null)
		{
			var ex = new ArgumentException($"Couldn't find endpoint");

			return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex));
		}

		string variant = null;
		if (endpoint.Variants != null && endpoint.Variants.Any() && TrueRandom.Next(0, 50) > 25)
		{
			variant = endpoint.Variants.Random().Name;
		}

		var sources = sourceFiles.GetRandomAmount(endpoint.SourcesRequired) as string[];

		return await GenerateImageAsync(endpoint.Name, variant, sources, cancellationToken);
	}

	public async Task<Object> GenerateImageAsync(string template, string variant, CancellationToken cancellationToken)
	{
		ImageGenerationImage imageTemplate = null;

		var endpoint = GenerationEndpoints.FirstOrDefault(e => e.Name.ToLowerInvariant().Equals(template.ToLowerInvariant()));

		if (endpoint is null)
		{
			var ex = new ArgumentException($"Couldn't find endpoint named \"{template}\"", nameof(template));

			return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex));
		}

		var sources = sourceFiles.GetRandomAmount(endpoint.SourcesRequired) as string[];

		return await GenerateImageAsync(template, variant, sources, cancellationToken);
	}
	
	public async Task<object> GenerateImageAsync(string template, string variant, string[] sources, CancellationToken cancellationToken)
	{
		while (true)
		{
			if(cancellationToken.IsCancellationRequested)
			{
				return null;
			}

			ImageGenerationImage imageTemplate = null;

			var endpoint = GenerationEndpoints.FirstOrDefault(e => e.Name.ToLowerInvariant().Equals(template.ToLowerInvariant()));

			if (endpoint is null)
			{
				var ex = new ArgumentException($"Couldn't find endpoint named \"{template}\"", nameof(template));

				return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex));
			}

			if (endpoint.SourcesRequired > sources.Length)
			{
				var ex = new ArgumentException("Not enough sources provided", nameof(sources));

				return GetResult(StatusCodes.Status400BadRequest, EventResult.FromFailureException(ex.Message, ex));
			}

			if (!string.IsNullOrWhiteSpace(variant))
			{
				imageTemplate = endpoint.Variants?
					.FirstOrDefault(v => v.Name.ToLowerInvariant().Equals(variant.ToLowerInvariant()));

				if (imageTemplate is null)
				{
					var ex = new ArgumentException(
						$"Invalid variant given \"{variant}\" for endpoint \"{template}\"",
						nameof(variant)
					);

					return GetResult(StatusCodes.Status400BadRequest, EventResult.FromFailureException(ex.Message, ex));
				}
			}
			else
			{
				imageTemplate = endpoint;
			}

			string image = Path.Combine(GenerationTemplateBaseFolder, !string.IsNullOrWhiteSpace(endpoint.Folder) ? endpoint.Folder : "", imageTemplate.Image);

			using MagickImage templateImage = new(System.IO.File.ReadAllBytes(image));
			using MagickImage baseImage = new(MagickColors.Transparent, templateImage.Width, templateImage.Height);

			baseImage.Format = MagickFormat.Png;

			if (imageTemplate.PlaceUnder)
			{
				await AddSourcesToImage(baseImage, imageTemplate, sources, cancellationToken);
			}

			baseImage.Composite(templateImage, Gravity.Northwest, CompositeOperator.Over);

			if (!imageTemplate.PlaceUnder)
			{
				await AddSourcesToImage(baseImage, imageTemplate, sources, cancellationToken);
			}

			return baseImage.ToByteArray();
		}

		return null;
	}

	static async Task AddSourcesToImage(MagickImage image, ImageGenerationImage template, IEnumerable<string> sources, CancellationToken cancellationToken)
	{
		while (true)
		{
			if(cancellationToken.IsCancellationRequested)
			{
				return;
			}
			
			int img = 0;
			for(int x = 0; x < template.Positions.Count; x++)
			{
				var position = template.Positions.ElementAtOrDefault(x);

				var src = sources.ElementAtOrDefault(img);

				Stream imageData = null;
				if (File.Exists(src))
				{
					imageData = new MemoryStream(await File.ReadAllBytesAsync(src));
				}
				else if (src.StartsWith("http"))
				{
					imageData = await HttpWebClient.GetStreamAsync(new Uri(src));
				}

				using MagickImage srcImg = new MagickImage(imageData);

				srcImg.Resize(position.Width, position.Height);

				if (position.Rotation != 0)
				{
					srcImg.Rotate(template.Rotate > 0 ? -position.Rotation : position.Rotation);
				}

				string background = !string.IsNullOrWhiteSpace(position.Background) ? $"#{position.Background}" : MagickColors.Transparent.ToHexString();

				var tmp = new MagickImage($"xc:{background}", position.Width, position.Height);

				tmp.Composite(srcImg, Gravity.Center, 0, 0, CompositeOperator.Over);

				image.Composite(tmp, Gravity.Northwest, position.X, position.Y, CompositeOperator.Over);

				if (img + 1 < sources.Count())
				{
					img++;
				}
			}

			return;
		}
	}

	public IEnumerable<ImageGenerationEndpointRead> GetGenerationEndpoints()
	{
		return GenerationEndpoints.Select(e => new ImageGenerationEndpointRead
		{
			Name = e.Name,
			SourcesRequired = e.SourcesRequired,
			Variants = e.Variants.Select(v => v.Name)
		});
	}
}