using MemberCenter.Api.Contracts;
using MemberCenter.Application.Abstractions;
using MemberCenter.Domain.Constants;
using MemberCenter.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;

namespace MemberCenter.Api.Controllers;

[ApiController]
[Route("subscriptions")]
public class SubscriptionsController : ControllerBase
{
    private static readonly HashSet<string> AllowedReasons = new(StringComparer.OrdinalIgnoreCase)
    {
        "hard_bounce",
        "soft_bounce_threshold",
        "complaint",
        "suppression"
    };
    private static readonly HashSet<string> BlacklistReasons = new(StringComparer.OrdinalIgnoreCase)
    {
        "hard_bounce",
        "soft_bounce_threshold",
        "suppression"
    };

    // TEST-ONLY SWITCH: keep this key stable so it is easy to find/remove after SES integration test.
    private const string DisableSubscriptionDryRunNoDbKey = "Testing:DisableSubscriptionDryRunNoDb";

    private readonly IEmailBlacklistService _emailBlacklistService;
    private readonly MemberCenterDbContext _dbContext;
    private readonly IConfiguration _configuration;
    private readonly ILogger<SubscriptionsController> _logger;

    public SubscriptionsController(
        IEmailBlacklistService emailBlacklistService,
        MemberCenterDbContext dbContext,
        IConfiguration configuration,
        ILogger<SubscriptionsController> logger)
    {
        _emailBlacklistService = emailBlacklistService;
        _dbContext = dbContext;
        _configuration = configuration;
        _logger = logger;
    }

    [Authorize]
    [HttpPost("disable")]
    public async Task<IActionResult> Disable([FromBody] DisableSubscriptionRequest request)
    {
        var hasTenantScope = HasScope(User, "newsletter:events.write");
        var hasGlobalScope = HasScope(User, "newsletter:events.write.global");
        if (!hasTenantScope && !hasGlobalScope)
        {
            return Forbid();
        }

        if (request.TenantId == Guid.Empty ||
            request.SubscriberId == Guid.Empty ||
            request.ListId == Guid.Empty ||
            string.IsNullOrWhiteSpace(request.Reason) ||
            string.IsNullOrWhiteSpace(request.DisabledBy))
        {
            return BadRequest("tenant_id, subscriber_id, list_id, reason, disabled_by are required.");
        }

        if (!AllowedReasons.Contains(request.Reason))
        {
            return BadRequest("reason must be one of: hard_bounce, soft_bounce_threshold, complaint, suppression.");
        }

        if (!hasGlobalScope && (!TryGetTenantId(User, out var tokenTenantId) || tokenTenantId != request.TenantId))
        {
            return Forbid();
        }

        // TEST-ONLY BEHAVIOR: in dry-run mode, do not execute DB read/write; only emit planned operations.
        if (_configuration.GetValue<bool>(DisableSubscriptionDryRunNoDbKey))
        {
            _logger.LogWarning(
                "TEST-ONLY DRY RUN ENABLED ({ConfigKey}). Skip DB access for /subscriptions/disable. Incoming payload: {@Payload}",
                DisableSubscriptionDryRunNoDbKey,
                request);

            _logger.LogInformation(
                "TEST-ONLY PLAN: would query newsletter_subscriptions by subscriber_id={SubscriberId}, list_id={ListId}, then verify tenant_id={TenantId}.",
                request.SubscriberId,
                request.ListId,
                request.TenantId);

            _logger.LogInformation(
                "TEST-ONLY PLAN: would blacklist resolved email with reason={Reason}, disabled_by={DisabledBy}, occurred_at={OccurredAt}.",
                request.Reason,
                request.DisabledBy,
                request.OccurredAt);

            return Ok(new
            {
                status = "dry_run_no_db",
                message = "DB access skipped by test flag.",
                config_key = DisableSubscriptionDryRunNoDbKey
            });
        }

        var target = await (
            from subscription in _dbContext.NewsletterSubscriptions
            join list in _dbContext.NewsletterLists on subscription.ListId equals list.Id
            where subscription.Id == request.SubscriberId && subscription.ListId == request.ListId
            select new
            {
                subscription.Email,
                list.TenantId
            })
            .FirstOrDefaultAsync();

        if (target is null)
        {
            return NotFound("Subscription not found.");
        }

        if (target.TenantId != request.TenantId)
        {
            return BadRequest("tenant_id does not match subscription/list tenant boundary.");
        }

        var normalizedEmail = target.Email.Trim().ToLowerInvariant();
        var shouldBlacklist = BlacklistReasons.Contains(request.Reason);
        var cancelledCount = 0;

        if (shouldBlacklist)
        {
            // hard/soft/suppression: cancel across all tenants for the same email.
            var subscriptions = await _dbContext.NewsletterSubscriptions
                .Where(s => s.Email.ToLower() == normalizedEmail && s.Status != SubscriptionStatus.Unsubscribed)
                .ToListAsync();

            foreach (var subscription in subscriptions)
            {
                subscription.Status = SubscriptionStatus.Unsubscribed;
            }

            cancelledCount = subscriptions.Count;
            await _dbContext.SaveChangesAsync();

            await _emailBlacklistService.AddOrUpdateAsync(
                target.Email,
                request.Reason,
                request.DisabledBy,
                request.OccurredAt);
        }
        else
        {
            // complaint: cancel only the target subscription; do not blacklist.
            var subscription = await _dbContext.NewsletterSubscriptions
                .FirstOrDefaultAsync(s => s.Id == request.SubscriberId && s.ListId == request.ListId);
            if (subscription is not null && subscription.Status != SubscriptionStatus.Unsubscribed)
            {
                subscription.Status = SubscriptionStatus.Unsubscribed;
                cancelledCount = 1;
                await _dbContext.SaveChangesAsync();
            }
        }

        return Ok(new
        {
            email = target.Email,
            status = shouldBlacklist ? "unsubscribed_blacklisted" : "unsubscribed",
            blacklisted = shouldBlacklist,
            cancelled_count = cancelledCount
        });
    }

    private static bool HasScope(System.Security.Claims.ClaimsPrincipal user, string scope)
    {
        var values = user.FindAll(OpenIddictConstants.Claims.Scope)
            .SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries));
        return values.Contains(scope, StringComparer.Ordinal);
    }

    private static bool TryGetTenantId(System.Security.Claims.ClaimsPrincipal user, out Guid tenantId)
    {
        tenantId = Guid.Empty;
        var value = user.FindFirst("tenant_id")?.Value;
        return !string.IsNullOrWhiteSpace(value) && Guid.TryParse(value, out tenantId);
    }
}
