using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Backend.Common;
using Backend.Contracts;
using Backend.Data;
using Backend.Domain;
using Microsoft.EntityFrameworkCore;

namespace Backend.Endpoints;

public static partial class PublicEndpoints
{
    private static readonly Regex PublicEmailPattern = new(@"^[^\s@]+@[^\s@]+\.[^\s@]+$", RegexOptions.Compiled);

    private static async Task<IResult> GetSponsors(int year, AwardsDbContext db)
    {
        var season = await db.Seasons
            .AsNoTracking()
            .FirstOrDefaultAsync(item => item.Year == year);

        if (season is null)
        {
            return Results.NotFound();
        }

        var settings = await db.SiteSettings.AsNoTracking().FirstOrDefaultAsync(item => item.Id == 1);
        if (settings is null || !settings.SponsorsVisible)
        {
            return Results.Ok(new PublicSponsorsResponse(year, []));
        }

        var sponsors = await db.Sponsors
            .AsNoTracking()
            .Where(item => item.SeasonId == season.Id && item.IsVisible)
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name)
            .Select(item => new SponsorDto(
                item.Id,
                item.SeasonId,
                item.Name,
                item.WebsiteUrl,
                item.LogoUrl,
                item.Description,
                item.Tier,
                item.SortOrder,
                item.IsVisible))
            .ToArrayAsync();

        return Results.Ok(new PublicSponsorsResponse(year, sponsors));
    }

    private static readonly JsonSerializerOptions ShowactJsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

    private static async Task<IResult> CreateShowactApplication(
        HttpContext context,
        CreateShowactApplicationRequest request,
        AwardsDbContext db)
    {
        var settings = await db.SiteSettings.AsNoTracking().FirstOrDefaultAsync(item => item.Id == 1, context.RequestAborted);
        if (settings is null || !ShowactApplicationSchedule.IsOpenNow(settings, DateOnly.FromDateTime(DateTime.UtcNow)))
        {
            return Results.BadRequest(new
            {
                message = string.IsNullOrWhiteSpace(settings?.ShowactApplicationDisabledMessage)
                    ? "Showact-Bewerbungen sind aktuell geschlossen."
                    : settings.ShowactApplicationDisabledMessage,
            });
        }

        var season = await db.Seasons.FirstOrDefaultAsync(item => item.IsCurrent, context.RequestAborted);
        if (season is null)
        {
            return Results.NotFound(new { message = "Aktuell ist kein Award-Jahr aktiv." });
        }

        if (!IsBlankOrValidJsonObject(request.FieldResponsesJson))
        {
            return Results.BadRequest(new { message = "Formulardaten konnten nicht gelesen werden." });
        }

        var schema = ParseShowactSchema(settings.ShowactFormSchemaJson);
        var hasDynamicForm = schema.Count > 0;

        if (hasDynamicForm)
        {
            Dictionary<string, string> responses;
            try
            {
                responses = string.IsNullOrWhiteSpace(request.FieldResponsesJson)
                    ? new Dictionary<string, string>()
                    : JsonSerializer.Deserialize<Dictionary<string, string>>(request.FieldResponsesJson, ShowactJsonOptions) ?? new();
            }
            catch (JsonException)
            {
                return Results.BadRequest(new { message = "Formulardaten konnten nicht gelesen werden." });
            }

            MergeLegacyShowactFieldsIntoResponses(schema, responses, request);

            var dynamicValidationError = ValidateShowactResponses(schema, responses);
            if (dynamicValidationError is not null)
            {
                return Results.BadRequest(new { message = dynamicValidationError });
            }

            var artistNameField = schema.FirstOrDefault(f => f.IsArtistName);
            var artistName = artistNameField is not null && responses.TryGetValue(artistNameField.Id, out var name) ? name.Trim() : "Unbekannt";
            var contactEmail = NormalizePublicText(request.ContactEmail, 180);
            var contactDiscord = NormalizePublicText(request.ContactDiscord, 120);
            var performanceType = NormalizePublicText(request.PerformanceType, 80);
            var description = NormalizePublicText(request.Description, 1000);
            var technicalNotes = NormalizePublicText(request.TechnicalNotes, 1000);
            var platformUrl = NormalizePublicText(request.PlatformUrl, 500);
            var referenceUrl = NormalizePublicText(request.ReferenceUrl, 500);
            var fieldResponsesJson = JsonSerializer.Serialize(responses, ShowactJsonOptions);

            var metadata = RequestMetadataReader.Read(context);
            var application = new ShowactApplication
            {
                SeasonId = season.Id,
                ArtistName = artistName[..Math.Min(artistName.Length, 120)],
                ContactEmail = contactEmail,
                ContactDiscord = contactDiscord,
                PlatformUrl = platformUrl,
                PerformanceType = performanceType,
                Description = description,
                TechnicalNotes = technicalNotes,
                ReferenceUrl = referenceUrl,
                FieldResponsesJson = fieldResponsesJson,
                Status = "pending",
                CreatedFromIp = metadata.ClientIp,
                UserAgent = metadata.UserAgent,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            db.ShowactApplications.Add(application);
            await db.SaveChangesAsync(context.RequestAborted);
            return Results.Ok(new { saved = true, applicationId = application.Id });
        }
        else
        {
            // Legacy fixed-fields path
            var artistName = NormalizePublicText(request.ArtistName, 120);
            var contactEmail = NormalizePublicText(request.ContactEmail, 180);
            var contactDiscord = NormalizePublicText(request.ContactDiscord, 120);
            var performanceType = NormalizePublicText(request.PerformanceType, 80);
            var description = NormalizePublicText(request.Description, 1000);
            var platformUrl = NormalizePublicText(request.PlatformUrl, 500);
            var referenceUrl = NormalizePublicText(request.ReferenceUrl, 500);

            if (string.IsNullOrWhiteSpace(artistName))
            {
                return Results.BadRequest(new { message = "Kuenstlername ist erforderlich." });
            }

            if (string.IsNullOrWhiteSpace(contactEmail) && string.IsNullOrWhiteSpace(contactDiscord))
            {
                return Results.BadRequest(new { message = "Bitte gib mindestens E-Mail oder Discord als Kontakt an." });
            }

            if (!IsBlankOrValidEmail(contactEmail))
            {
                return Results.BadRequest(new { message = "E-Mail muss eine gueltige E-Mail-Adresse sein." });
            }

            if (string.IsNullOrWhiteSpace(performanceType) || string.IsNullOrWhiteSpace(description))
            {
                return Results.BadRequest(new { message = "Art des Showacts und Beschreibung sind erforderlich." });
            }

            if (!IsBlankOrHttpUrl(platformUrl) || !IsBlankOrHttpUrl(referenceUrl))
            {
                return Results.BadRequest(new { message = "Links muessen gueltige http(s)-URLs sein." });
            }

            var metadata = RequestMetadataReader.Read(context);
            var application = new ShowactApplication
            {
                SeasonId = season.Id,
                ArtistName = artistName,
                ContactEmail = contactEmail,
                ContactDiscord = contactDiscord,
                PlatformUrl = platformUrl,
                PerformanceType = performanceType,
                Description = description,
                TechnicalNotes = NormalizePublicText(request.TechnicalNotes, 1000),
                ReferenceUrl = referenceUrl,
                Status = "pending",
                CreatedFromIp = metadata.ClientIp,
                UserAgent = metadata.UserAgent,
                CreatedAt = DateTimeOffset.UtcNow,
            };

            db.ShowactApplications.Add(application);
            await db.SaveChangesAsync(context.RequestAborted);
            return Results.Ok(new { saved = true, applicationId = application.Id });
        }
    }

    private sealed class ShowactFieldDefinition
    {
        [JsonPropertyName("id")] public string Id { get; set; } = "";
        [JsonPropertyName("type")] public string Type { get; set; } = "";
        [JsonPropertyName("label")] public string Label { get; set; } = "";
        [JsonPropertyName("required")] public bool Required { get; set; }
        [JsonPropertyName("isArtistName")] public bool IsArtistName { get; set; }
        [JsonPropertyName("maxLength")] public int MaxLength { get; set; }
    }

    private static string NormalizePublicText(string? value, int maxLength)
    {
        var trimmed = (value ?? string.Empty).Trim();
        return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength];
    }

    private static bool IsBlankOrHttpUrl(string value) =>
        string.IsNullOrWhiteSpace(value)
        || (Uri.TryCreate(value, UriKind.Absolute, out var uri)
            && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps));

    private static bool IsBlankOrValidEmail(string value) =>
        string.IsNullOrWhiteSpace(value) || PublicEmailPattern.IsMatch(value);

    private static bool IsBlankOrValidJsonObject(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return true;
        }

        try
        {
            using var document = JsonDocument.Parse(value);
            return document.RootElement.ValueKind == JsonValueKind.Object;
        }
        catch (JsonException)
        {
            return false;
        }
    }

    private static List<ShowactFieldDefinition> ParseShowactSchema(string? schemaJson)
    {
        if (string.IsNullOrWhiteSpace(schemaJson) || schemaJson == "[]")
        {
            return [];
        }

        try
        {
            return JsonSerializer.Deserialize<List<ShowactFieldDefinition>>(schemaJson, ShowactJsonOptions)?
                .Where(field => !string.IsNullOrWhiteSpace(field.Id))
                .ToList() ?? [];
        }
        catch (JsonException)
        {
            return [];
        }
    }

    private static string? ValidateShowactResponses(
        IReadOnlyCollection<ShowactFieldDefinition> schema,
        IDictionary<string, string> responses)
    {
        foreach (var field in schema)
        {
            var value = responses.TryGetValue(field.Id, out var rawValue)
                ? NormalizePublicText(rawValue, ResolveShowactMaxLength(field))
                : string.Empty;
            responses[field.Id] = value;

            if (field.Required && string.IsNullOrWhiteSpace(value))
            {
                return string.Equals(field.Type, "checkbox", StringComparison.OrdinalIgnoreCase)
                    ? $"Bitte bestaetige: {field.Label}"
                    : $"{field.Label} ist erforderlich.";
            }

            if (string.IsNullOrWhiteSpace(value))
            {
                continue;
            }

            if (string.Equals(field.Type, "email", StringComparison.OrdinalIgnoreCase) && !PublicEmailPattern.IsMatch(value))
            {
                return $"{field.Label} muss eine gueltige E-Mail-Adresse sein.";
            }

            if (string.Equals(field.Type, "url", StringComparison.OrdinalIgnoreCase) && !IsBlankOrHttpUrl(value))
            {
                return $"{field.Label} muss ein gueltiger http(s)-Link sein.";
            }
        }

        return null;
    }

    private static int ResolveShowactMaxLength(ShowactFieldDefinition field)
    {
        if (field.MaxLength > 0)
        {
            return Math.Min(field.MaxLength, 2000);
        }

        return string.Equals(field.Type, "textarea", StringComparison.OrdinalIgnoreCase) ? 1000 : 500;
    }

    private static void MergeLegacyShowactFieldsIntoResponses(
        IReadOnlyCollection<ShowactFieldDefinition> schema,
        IDictionary<string, string> responses,
        CreateShowactApplicationRequest request)
    {
        var artistField = schema.FirstOrDefault(field => field.IsArtistName);
        MergeResponseValue(artistField, request.ArtistName, responses, 120);

        var emailField = schema.FirstOrDefault(field => string.Equals(field.Type, "email", StringComparison.OrdinalIgnoreCase));
        MergeResponseValue(emailField, request.ContactEmail, responses, 180);

        var discordField = schema.FirstOrDefault(field => ContainsAny(field, "discord"));
        MergeResponseValue(discordField, request.ContactDiscord, responses, 120);

        var platformField = schema.FirstOrDefault(field => string.Equals(field.Type, "url", StringComparison.OrdinalIgnoreCase) && ContainsAny(field, "platform", "kanal", "profil", "channel"));
        MergeResponseValue(platformField, request.PlatformUrl, responses, 500);

        var referenceField = schema.FirstOrDefault(field => string.Equals(field.Type, "url", StringComparison.OrdinalIgnoreCase) && ContainsAny(field, "referenz", "reference"));
        MergeResponseValue(referenceField, request.ReferenceUrl, responses, 500);

        var performanceField = schema.FirstOrDefault(field =>
            ContainsAnyId(field, "performance_type", "showact_type", "show_type", "showact_roles", "roles")
            || ContainsAnyLabel(field, "performance", "showact-art", "showact art", "art des showacts", "wofür möchtest", "wofuer moechtest", "bewerben"));
        MergeResponseValue(performanceField, request.PerformanceType, responses, 80);

        var descriptionField = schema.FirstOrDefault(field =>
            string.Equals(field.Type, "textarea", StringComparison.OrdinalIgnoreCase)
            && (ContainsAnyId(field, "description", "beschreibung", "show_description")
                || ContainsAnyLabel(field, "beschreibung", "idee", "was moechtest", "was möchtest", "zeigen")));
        MergeResponseValue(descriptionField, request.Description, responses, 1000);

        var technicalNotesField = schema.FirstOrDefault(field => ContainsAny(field, "technical_notes", "technik", "technical", "setup", "timing"));
        MergeResponseValue(technicalNotesField, request.TechnicalNotes, responses, 1000);
    }

    private static void MergeResponseValue(
        ShowactFieldDefinition? field,
        string? requestValue,
        IDictionary<string, string> responses,
        int maxLength)
    {
        if (field is null)
        {
            return;
        }

        if (responses.TryGetValue(field.Id, out var existingValue) && !string.IsNullOrWhiteSpace(existingValue))
        {
            return;
        }

        var normalized = NormalizePublicText(requestValue, maxLength);
        if (!string.IsNullOrWhiteSpace(normalized))
        {
            responses[field.Id] = normalized;
        }
    }

    private static bool ContainsAny(ShowactFieldDefinition field, params string[] needles)
    {
        var haystack = $"{field.Id} {field.Label}".ToLowerInvariant();
        return needles.Any(haystack.Contains);
    }

    private static bool ContainsAnyId(ShowactFieldDefinition field, params string[] needles)
    {
        var haystack = field.Id.ToLowerInvariant();
        return needles.Any(haystack.Contains);
    }

    private static bool ContainsAnyLabel(ShowactFieldDefinition field, params string[] needles)
    {
        var haystack = field.Label.ToLowerInvariant();
        return needles.Any(haystack.Contains);
    }
}
