using MemberCenter.Api.Contracts;
using MemberCenter.Application.Abstractions;
using MemberCenter.Application.Constants;
using MemberCenter.Infrastructure.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using OpenIddict.Abstractions;
using Microsoft.Extensions.Options;

namespace MemberCenter.Api.Controllers;

[ApiController]
[Route("newsletter")]
public class NewsletterController : ControllerBase
{
    private readonly INewsletterService _newsletterService;
    private readonly INewsletterListService _newsletterListService;
    private readonly NewsletterTokenOptions _tokenOptions;

    public NewsletterController(
        INewsletterService newsletterService,
        INewsletterListService newsletterListService,
        IOptions<NewsletterTokenOptions> tokenOptions)
    {
        _newsletterService = newsletterService;
        _newsletterListService = newsletterListService;
        _tokenOptions = tokenOptions.Value;
    }

    [HttpPost("subscribe")]
    [Authorize]
    [EnableRateLimiting(RateLimitPolicyNames.PublicNewsletterSubscribe)]
    public async Task<IActionResult> Subscribe([FromBody] SubscribeRequest request)
    {
        var authorizationFailure = await ValidateTenantListAccessAsync(request.ListId, "newsletter:subscriptions.write");
        if (authorizationFailure is not null)
        {
            return authorizationFailure;
        }

        var result = await _newsletterService.SubscribeAsync(request.ListId, request.Email, request.Preferences);
        if (result is null)
        {
            return NotFound("List not found.");
        }

        return Ok(new
        {
            id = result.Subscription.Id,
            list_id = result.Subscription.ListId,
            email = result.Subscription.Email,
            status = result.Subscription.Status,
            created_at = result.Subscription.CreatedAt,
            confirm_token = result.ConfirmToken
        });
    }

    [HttpGet("confirm")]
    public async Task<IActionResult> Confirm([FromQuery] string token)
    {
        var subscription = await _newsletterService.ConfirmAsync(token);
        if (subscription is null)
        {
            return NotFound("Invalid token.");
        }

        return Ok(new
        {
            id = subscription.Id,
            list_id = subscription.ListId,
            email = subscription.Email,
            status = subscription.Status,
            created_at = subscription.CreatedAt
        });
    }

    [HttpPost("unsubscribe")]
    public async Task<IActionResult> Unsubscribe([FromBody] UnsubscribeRequest request)
    {
        var subscription = await _newsletterService.UnsubscribeAsync(request.Token);
        if (subscription is null)
        {
            return NotFound("Invalid token.");
        }

        return Ok(new
        {
            id = subscription.Id,
            list_id = subscription.ListId,
            email = subscription.Email,
            status = subscription.Status,
            created_at = subscription.CreatedAt
        });
    }

    [HttpPost("unsubscribe-token")]
    [Authorize]
    [EnableRateLimiting(RateLimitPolicyNames.PublicNewsletterUnsubscribeToken)]
    public async Task<IActionResult> IssueUnsubscribeToken([FromBody] IssueUnsubscribeTokenRequest request)
    {
        if (request.ListId == Guid.Empty || string.IsNullOrWhiteSpace(request.Email))
        {
            return BadRequest("Both list_id and email are required.");
        }

        var authorizationFailure = await ValidateTenantListAccessAsync(request.ListId, "newsletter:subscriptions.write");
        if (authorizationFailure is not null)
        {
            return authorizationFailure;
        }

        var token = await _newsletterService.IssueUnsubscribeTokenAsync(request.ListId, request.Email);
        if (token is null)
        {
            return NotFound("Subscription not found.");
        }

        return Ok(new
        {
            unsubscribe_token = token
        });
    }

    [Authorize]
    [EnableRateLimiting(RateLimitPolicyNames.NewsletterOneClickToken)]
    [HttpPost("one-click-unsubscribe-token")]
    public async Task<IActionResult> IssueOneClickUnsubscribeToken([FromBody] IssueOneClickUnsubscribeTokenRequest 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.ListId == Guid.Empty || request.SubscriberId == Guid.Empty)
        {
            return BadRequest("tenant_id, list_id, subscriber_id are required.");
        }

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

        var token = await _newsletterService.IssueOneClickUnsubscribeTokenAsync(request.TenantId, request.ListId, request.SubscriberId);
        if (token is null)
        {
            return NotFound("Subscription not found.");
        }

        return Ok(new
        {
            unsubscribe_token = token
        });
    }

    [Authorize]
    [EnableRateLimiting(RateLimitPolicyNames.NewsletterOneClickToken)]
    [HttpPost("one-click-unsubscribe-tokens")]
    public async Task<IActionResult> IssueOneClickUnsubscribeTokens([FromBody] IssueOneClickUnsubscribeTokensRequest 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.ListId == Guid.Empty || request.SubscriberIds is null || request.SubscriberIds.Count == 0)
        {
            return BadRequest("tenant_id, list_id, subscriber_ids are required.");
        }

        if (request.SubscriberIds.Count > _tokenOptions.OneClickBatchSizeLimit)
        {
            return BadRequest($"subscriber_ids exceeds maximum batch size ({_tokenOptions.OneClickBatchSizeLimit}).");
        }

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

        var items = await _newsletterService.IssueOneClickUnsubscribeTokensAsync(request.TenantId, request.ListId, request.SubscriberIds);
        return Ok(new
        {
            items = items.Select(x => new
            {
                subscriber_id = x.SubscriberId,
                unsubscribe_token = x.UnsubscribeToken,
                status = x.Status
            })
        });
    }

    [HttpGet("preferences")]
    [Authorize]
    public async Task<IActionResult> Preferences([FromQuery(Name = "list_id")] Guid? listId, [FromQuery] string? email)
    {
        if (!listId.HasValue || listId.Value == Guid.Empty || string.IsNullOrWhiteSpace(email))
        {
            return BadRequest("Both list_id and email are required.");
        }

        var authorizationFailure = await ValidateTenantListAccessAsync(listId.Value, "newsletter:subscriptions.read");
        if (authorizationFailure is not null)
        {
            return authorizationFailure;
        }

        var subscription = await _newsletterService.GetPreferencesAsync(listId.Value, email);
        if (subscription is null)
        {
            return NotFound("Subscription not found.");
        }

        return Ok(new
        {
            id = subscription.Id,
            list_id = subscription.ListId,
            email = subscription.Email,
            status = subscription.Status,
            preferences = subscription.Preferences
        });
    }

    [HttpPost("preferences")]
    [Authorize]
    public async Task<IActionResult> UpdatePreferences([FromBody] UpdatePreferencesRequest request)
    {
        if (request.ListId == Guid.Empty || string.IsNullOrWhiteSpace(request.Email))
        {
            return BadRequest("Both list_id and email are required.");
        }

        var authorizationFailure = await ValidateTenantListAccessAsync(request.ListId, "newsletter:subscriptions.write");
        if (authorizationFailure is not null)
        {
            return authorizationFailure;
        }

        var subscription = await _newsletterService.UpdatePreferencesAsync(request.ListId, request.Email, request.Preferences);
        if (subscription is null)
        {
            return NotFound("Subscription not found.");
        }

        return Ok(new
        {
            id = subscription.Id,
            list_id = subscription.ListId,
            email = subscription.Email,
            status = subscription.Status,
            preferences = subscription.Preferences
        });
    }

    [Authorize]
    [HttpGet("subscriptions")]
    public async Task<IActionResult> ListSubscriptions([FromQuery(Name = "list_id")] Guid listId)
    {
        var hasTenantScope = HasScope(User, "newsletter:list.read");
        var hasGlobalScope = HasScope(User, "newsletter:list.read.global");
        if (!hasTenantScope && !hasGlobalScope)
        {
            return Forbid();
        }

        if (listId == Guid.Empty)
        {
            return BadRequest("list_id is required.");
        }

        var list = await _newsletterListService.GetAsync(listId);
        if (list is null)
        {
            return NotFound();
        }

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

        var subscriptions = await _newsletterService.ListSubscriptionsAsync(listId);
        return Ok(subscriptions.Select(s => new
        {
            id = s.Id,
            list_id = s.ListId,
            email = s.Email,
            status = s.Status,
            preferences = s.Preferences,
            created_at = s.CreatedAt
        }));
    }

    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 async Task<IActionResult?> ValidateTenantListAccessAsync(Guid listId, string scope)
    {
        if (!HasScope(User, scope))
        {
            return Forbid();
        }

        if (listId == Guid.Empty)
        {
            return BadRequest("list_id is required.");
        }

        var list = await _newsletterListService.GetAsync(listId);
        if (list is null)
        {
            return NotFound("List not found.");
        }

        return TryGetTenantId(User, out var tenantId) && tenantId == list.TenantId
            ? null
            : Forbid();
    }

    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);
    }
}
