using System.Security.Cryptography;
using System.Text;
using MemberCenter.Api.Contracts;
using MemberCenter.Domain.Entities;
using MemberCenter.Infrastructure.Configuration;
using MemberCenter.Infrastructure.Identity;
using MemberCenter.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
using Microsoft.Extensions.Options;

namespace MemberCenter.Api.Controllers;

[ApiController]
[Route("file-access/download-tokens")]
public sealed class FileAccessController : ControllerBase
{
    private const string DownloadScope = "files:download.read";
    private const string TokenPurpose = "file_access_download";
    private readonly MemberCenterDbContext _dbContext;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly FileAccessTokenOptions _tokenOptions;

    public FileAccessController(
        MemberCenterDbContext dbContext,
        UserManager<ApplicationUser> userManager,
        IOptions<FileAccessTokenOptions> tokenOptions)
    {
        _dbContext = dbContext;
        _userManager = userManager;
        _tokenOptions = tokenOptions.Value;
    }

    [Authorize(Policy = "FilesDownloadDelegate")]
    [HttpPost("")]
    public async Task<IActionResult> Issue([FromBody] IssueFileDownloadTokenRequest request)
    {
        var validationError = ValidateBoundary(request.TenantId, request.FileId, request.ObjectKey, request.Method);
        if (validationError is not null)
        {
            return BadRequest(validationError);
        }

        if (!IsTenantAllowed(request.TenantId))
        {
            return Forbid();
        }

        if (request.UserId == Guid.Empty)
        {
            return BadRequest("user_id is required.");
        }

        var user = await _userManager.FindByIdAsync(request.UserId.ToString());
        if (user is null || user.DisabledAt.HasValue)
        {
            return BadRequest("user_id is invalid or disabled.");
        }

        var lifetime = ResolveLifetime(request.ExpiresInSeconds);
        var expiresAt = DateTimeOffset.UtcNow.Add(lifetime);
        var token = $"fdt_{WebEncoders.Base64UrlEncode(RandomNumberGenerator.GetBytes(32))}";

        var record = new FileAccessDownloadToken
        {
            Id = Guid.NewGuid(),
            TokenHash = HashToken(token),
            TenantId = request.TenantId,
            UserId = request.UserId,
            FileId = NormalizeOptional(request.FileId),
            ObjectKey = NormalizeOptional(request.ObjectKey),
            Method = "GET",
            Scope = DownloadScope,
            IssuedByClientId = User.GetClaim(OpenIddictConstants.Claims.ClientId),
            ExpiresAt = expiresAt
        };

        _dbContext.FileAccessDownloadTokens.Add(record);
        await _dbContext.SaveChangesAsync(HttpContext.RequestAborted);

        return Ok(new
        {
            token,
            token_type = "file_download",
            expires_at = expiresAt,
            tenant_id = record.TenantId,
            user_id = record.UserId,
            file_id = record.FileId,
            object_key = record.ObjectKey,
            method = record.Method,
            scope = record.Scope
        });
    }

    [Authorize(Policy = "FilesDownloadRead")]
    [HttpPost("validate")]
    public async Task<IActionResult> Validate([FromBody] ValidateFileDownloadTokenRequest request)
    {
        var validationError = ValidateBoundary(request.TenantId, request.FileId, request.ObjectKey, request.Method);
        if (validationError is not null || string.IsNullOrWhiteSpace(request.Token))
        {
            return Ok(new { active = false });
        }

        if (!IsTenantAllowed(request.TenantId))
        {
            return Ok(new { active = false });
        }

        var tokenHash = HashToken(request.Token);
        var now = DateTimeOffset.UtcNow;
        var record = await _dbContext.FileAccessDownloadTokens
            .SingleOrDefaultAsync(token =>
                token.TokenHash == tokenHash
                && token.RevokedAt == null
                && token.ExpiresAt > now,
                HttpContext.RequestAborted);

        if (record is null
            || record.TenantId != request.TenantId
            || !string.Equals(record.Method, "GET", StringComparison.OrdinalIgnoreCase)
            || !string.Equals(record.FileId, NormalizeOptional(request.FileId), StringComparison.Ordinal)
            || !string.Equals(record.ObjectKey, NormalizeOptional(request.ObjectKey), StringComparison.Ordinal))
        {
            return Ok(new { active = false });
        }

        var user = await _userManager.FindByIdAsync(record.UserId.ToString());
        if (user is null || user.DisabledAt.HasValue)
        {
            return Ok(new { active = false });
        }

        record.LastValidatedAt = now;
        await _dbContext.SaveChangesAsync(HttpContext.RequestAborted);

        return Ok(new
        {
            active = true,
            tenant_id = record.TenantId,
            user_id = record.UserId,
            file_id = record.FileId,
            object_key = record.ObjectKey,
            method = record.Method,
            scope = record.Scope,
            expires_at = record.ExpiresAt
        });
    }

    private bool IsTenantAllowed(Guid tenantId)
    {
        var tokenTenantId = User.FindFirst("tenant_id")?.Value;
        return Guid.TryParse(tokenTenantId, out var parsed) && parsed == tenantId;
    }

    private static string? ValidateBoundary(Guid tenantId, string? fileId, string? objectKey, string? method)
    {
        if (tenantId == Guid.Empty)
        {
            return "tenant_id is required.";
        }

        if (!string.Equals(method ?? "GET", "GET", StringComparison.OrdinalIgnoreCase))
        {
            return "Only method=GET is supported for file download tokens.";
        }

        if (string.IsNullOrWhiteSpace(fileId) && string.IsNullOrWhiteSpace(objectKey))
        {
            return "file_id or object_key is required.";
        }

        return null;
    }

    private TimeSpan ResolveLifetime(int? expiresInSeconds)
    {
        if (!expiresInSeconds.HasValue)
        {
            return TimeSpan.FromSeconds(_tokenOptions.DefaultLifetimeSeconds);
        }

        var requested = TimeSpan.FromSeconds(expiresInSeconds.Value);
        var minimum = TimeSpan.FromSeconds(_tokenOptions.MinimumLifetimeSeconds);
        if (requested < minimum)
        {
            return minimum;
        }

        var maximum = TimeSpan.FromSeconds(_tokenOptions.MaximumLifetimeSeconds);
        return requested > maximum ? maximum : requested;
    }

    private static string? NormalizeOptional(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }

    private static string HashToken(string token)
    {
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes($"{TokenPurpose}:{token}"));
        return Convert.ToHexString(bytes).ToLowerInvariant();
    }
}
