using System.Net;
using System.Net.Mail;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using EntKube.Web.Data;

namespace EntKube.Web.Services;

public class NotificationService(
    IHttpClientFactory httpClientFactory,
    IDbContextFactory<ApplicationDbContext> dbFactory,
    IConfiguration configuration,
    ILogger<NotificationService> logger)
{
    public async Task SendAsync(
        AlertIncident incident,
        NotificationChannel channel,
        bool isFiring,
        CancellationToken ct = default)
    {
        if (!channel.IsEnabled) return;
        if (!MeetsSeverityFilter(incident.Severity, channel.SeverityFilter)) return;

        bool success = false;
        string? error = null;

        try
        {
            success = channel.Type switch
            {
                NotificationChannelType.Slack   => await SendSlackAsync(incident, channel, isFiring, ct),
                NotificationChannelType.Teams   => await SendTeamsAsync(incident, channel, isFiring, ct),
                NotificationChannelType.Email   => await SendEmailAsync(incident, channel, isFiring),
                NotificationChannelType.Webhook => await SendWebhookAsync(incident, channel, isFiring, ct),
                _ => false
            };
        }
        catch (Exception ex)
        {
            error = ex.Message;
            logger.LogWarning(ex, "Notification failed for channel {Channel} ({Type})", channel.Name, channel.Type);
        }

        using ApplicationDbContext db = dbFactory.CreateDbContext();
        db.NotificationDeliveries.Add(new NotificationDelivery
        {
            IncidentId = incident.Id,
            ChannelId = channel.Id,
            IsFiring = isFiring,
            Success = success,
            Error = error?.Length > 1000 ? error[..1000] : error
        });
        await db.SaveChangesAsync(ct);
    }

    public async Task SendTestAsync(NotificationChannel channel, CancellationToken ct = default)
    {
        AlertIncident fake = new()
        {
            Id = Guid.Empty,
            ClusterId = Guid.Empty,
            Fingerprint = "test",
            AlertName = "TestAlert",
            Severity = "info",
            Summary = "This is a test notification from EntKube.",
            StartsAt = DateTime.UtcNow,
            Status = IncidentStatus.Active,
            Cluster = new KubernetesCluster { Name = "test-cluster", ApiServerUrl = "" }
        };

        bool success = false;
        string? error = null;
        try
        {
            success = channel.Type switch
            {
                NotificationChannelType.Slack   => await SendSlackAsync(fake, channel, true, ct),
                NotificationChannelType.Teams   => await SendTeamsAsync(fake, channel, true, ct),
                NotificationChannelType.Email   => await SendEmailAsync(fake, channel, true),
                NotificationChannelType.Webhook => await SendWebhookAsync(fake, channel, true, ct),
                _ => false
            };
        }
        catch (Exception ex)
        {
            error = ex.Message;
        }

        if (!success)
            throw new InvalidOperationException(error ?? "Test notification failed");
    }

    private async Task<bool> SendSlackAsync(
        AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct)
    {
        using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson);
        string webhookUrl = config.RootElement.GetProperty("webhookUrl").GetString()
            ?? throw new InvalidOperationException("Slack webhookUrl missing");

        string emoji = incident.Severity switch
        {
            "critical" => ":red_circle:",
            "warning"  => ":warning:",
            _          => ":information_source:"
        };
        string state = isFiring ? "FIRING" : "RESOLVED";
        string text = $"{emoji} *[{state}] {incident.AlertName}*\n" +
                      $"Severity: {incident.Severity} | Cluster: {incident.Cluster?.Name ?? incident.ClusterId.ToString()}\n" +
                      $"Started: {incident.StartsAt:u}\n" +
                      $"{(string.IsNullOrEmpty(incident.Summary) ? "" : incident.Summary)}";

        string body = JsonSerializer.Serialize(new { text });
        using HttpClient http = httpClientFactory.CreateClient("Notifications");
        using StringContent content = new(body, Encoding.UTF8, "application/json");
        HttpResponseMessage response = await http.PostAsync(webhookUrl, content, ct);
        return response.IsSuccessStatusCode;
    }

    private async Task<bool> SendTeamsAsync(
        AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct)
    {
        using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson);
        string webhookUrl = config.RootElement.GetProperty("webhookUrl").GetString()
            ?? throw new InvalidOperationException("Teams webhookUrl missing");

        string color = incident.Severity switch
        {
            "critical" => "attention",
            "warning"  => "warning",
            _          => "accent"
        };
        string state = isFiring ? "FIRING" : "RESOLVED";

        object card = new
        {
            type = "message",
            attachments = new[]
            {
                new
                {
                    contentType = "application/vnd.microsoft.card.adaptive",
                    content = new
                    {
                        type = "AdaptiveCard",
                        version = "1.4",
                        body = new object[]
                        {
                            new { type = "TextBlock", text = $"[{state}] {incident.AlertName}", weight = "Bolder", size = "Medium", color },
                            new { type = "FactSet", facts = new[]
                            {
                                new { title = "Severity", value = incident.Severity },
                                new { title = "Cluster", value = incident.Cluster?.Name ?? incident.ClusterId.ToString() },
                                new { title = "Started", value = incident.StartsAt.ToString("u") },
                                new { title = "Summary", value = string.IsNullOrEmpty(incident.Summary) ? "-" : incident.Summary }
                            }}
                        }
                    }
                }
            }
        };

        string body = JsonSerializer.Serialize(card);
        using HttpClient http = httpClientFactory.CreateClient("Notifications");
        using StringContent content = new(body, Encoding.UTF8, "application/json");
        HttpResponseMessage response = await http.PostAsync(webhookUrl, content, ct);
        return response.IsSuccessStatusCode;
    }

    private Task<bool> SendEmailAsync(AlertIncident incident, NotificationChannel channel, bool isFiring)
    {
        using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson);
        string to = config.RootElement.GetProperty("to").GetString()
            ?? throw new InvalidOperationException("Email 'to' missing");

        string? smtpHost = configuration["Smtp:Host"];
        if (string.IsNullOrEmpty(smtpHost))
        {
            logger.LogWarning("SMTP not configured — skipping email notification for channel {Channel}", channel.Name);
            return Task.FromResult(false);
        }

        int smtpPort = configuration.GetValue<int>("Smtp:Port", 587);
        string from = configuration["Smtp:FromAddress"] ?? "alerts@entkube.io";
        string? smtpUser = configuration["Smtp:Username"];
        string? smtpPass = configuration["Smtp:Password"];

        string state = isFiring ? "FIRING" : "RESOLVED";
        string subject = $"[EntKube] [{state}] {incident.AlertName} ({incident.Severity})";
        string body = $"Alert: {incident.AlertName}\n" +
                      $"Status: {state}\n" +
                      $"Severity: {incident.Severity}\n" +
                      $"Cluster: {incident.Cluster?.Name ?? incident.ClusterId.ToString()}\n" +
                      $"Started: {incident.StartsAt:u}\n" +
                      $"Summary: {incident.Summary}\n" +
                      $"Description: {incident.Description}";

        using SmtpClient smtp = new(smtpHost, smtpPort);
        smtp.EnableSsl = true;
        if (!string.IsNullOrEmpty(smtpUser))
            smtp.Credentials = new NetworkCredential(smtpUser, smtpPass);

        using MailMessage mail = new(from, to, subject, body);
        smtp.Send(mail);
        return Task.FromResult(true);
    }

    private async Task<bool> SendWebhookAsync(
        AlertIncident incident, NotificationChannel channel, bool isFiring, CancellationToken ct)
    {
        using JsonDocument config = JsonDocument.Parse(channel.ConfigurationJson);
        string url = config.RootElement.GetProperty("url").GetString()
            ?? throw new InvalidOperationException("Webhook url missing");

        string? token = config.RootElement.TryGetProperty("token", out JsonElement t)
            ? t.GetString()
            : null;

        object payload = new
        {
            firing = isFiring,
            alertName = incident.AlertName,
            severity = incident.Severity,
            summary = incident.Summary,
            description = incident.Description,
            clusterName = incident.Cluster?.Name ?? incident.ClusterId.ToString(),
            startsAt = incident.StartsAt,
            endsAt = incident.EndsAt,
            status = incident.Status.ToString()
        };

        using HttpClient http = httpClientFactory.CreateClient("Notifications");
        if (!string.IsNullOrEmpty(token))
            http.DefaultRequestHeaders.Authorization =
                new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        using StringContent content = new(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
        HttpResponseMessage response = await http.PostAsync(url, content, ct);
        return response.IsSuccessStatusCode;
    }

    private static bool MeetsSeverityFilter(string severity, AlertSeverityFilter filter) => filter switch
    {
        AlertSeverityFilter.CriticalOnly   => severity == "critical",
        AlertSeverityFilter.WarningAndAbove => severity is "critical" or "warning",
        _ => true
    };
}
