using System.Collections.Concurrent;
using System.Web;
using foodmarket.Application.Common.Email;
using foodmarket.Infrastructure.Identity;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace foodmarket.Api.Controllers;

/// <summary>Восстановление пароля. Эндпоинты anonymous, защищены простым
/// IP-rate-limit'ом (3 попытки в час на IP), чтобы не было spam-attack.
/// Ответ /forgot-password всегда 200 — анти-юзер-энумерация (не палим
/// существование email).</summary>
[ApiController]
[AllowAnonymous]
[Route("api/auth")]
public class AuthForgotPasswordController : ControllerBase
{
    private readonly UserManager<User> _userMgr;
    private readonly AppDbContext _db;
    private readonly IEmailSender _email;
    private readonly ILogger<AuthForgotPasswordController> _logger;

    // In-memory rate-limit. Для одного API-инстанса достаточно; при scale-out
    // понадобится Redis. Кладём timestamps попыток per IP, рубим >3 за час.
    private static readonly ConcurrentDictionary<string, List<DateTime>> _ipAttempts = new();
    private static readonly TimeSpan _rateLimitWindow = TimeSpan.FromHours(1);
    private const int _maxAttemptsPerWindow = 3;

    public AuthForgotPasswordController(
        UserManager<User> userMgr, AppDbContext db, IEmailSender email,
        ILogger<AuthForgotPasswordController> logger)
    {
        _userMgr = userMgr; _db = db; _email = email; _logger = logger;
    }

    public record ForgotInput(string Email);
    public record ResetInput(string Email, string Token, string NewPassword);

    [HttpPost("forgot-password")]
    public async Task<IActionResult> Forgot([FromBody] ForgotInput input, CancellationToken ct)
    {
        var ip = HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown";
        if (!CheckRateLimit(ip))
        {
            return StatusCode(StatusCodes.Status429TooManyRequests, new
            {
                error = "Слишком много попыток. Попробуйте через час.",
            });
        }

        // Универсальный 200 чтобы не палить существование email.
        // Реально письмо шлём только если юзер найден И активен И имеет email.
        if (string.IsNullOrWhiteSpace(input.Email))
            return Ok(new { ok = true });

        try
        {
            var user = await _userMgr.FindByEmailAsync(input.Email.Trim());
            if (user is not null && user.IsActive && !string.IsNullOrEmpty(user.Email))
            {
                var token = await _userMgr.GeneratePasswordResetTokenAsync(user);
                var resetUrl = BuildResetUrl(user.Email!, token);
                var body =
                    $"Здравствуйте.\n\n" +
                    $"Кто-то запросил восстановление пароля для вашего аккаунта Food Market ({user.Email}).\n" +
                    $"Если это были вы — перейдите по ссылке (действительна 1 час):\n\n" +
                    $"{resetUrl}\n\n" +
                    $"Если вы не запрашивали восстановление — просто игнорируйте это письмо.\n";
                try
                {
                    await _email.SendAsync(user.Email!, "Food Market — восстановление пароля", body, ct);
                    _logger.LogInformation("Forgot-password email sent to {Email}", user.Email);
                }
                catch (EmailNotConfiguredException ex)
                {
                    // Не падаем 500 — это плохая UX. Просто логируем и
                    // отдаём 200, чтобы юзер не ждал зря; SuperAdmin увидит
                    // в логах что нужно настроить SMTP.
                    _logger.LogError(ex, "Forgot-password skipped: SMTP not configured");
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Forgot-password email failed for {Email}", user.Email);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Forgot-password unexpected error");
        }
        return Ok(new { ok = true });
    }

    [HttpPost("reset-password")]
    public async Task<IActionResult> Reset([FromBody] ResetInput input, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(input.Email) || string.IsNullOrWhiteSpace(input.Token))
            return BadRequest(new { error = "Некорректная ссылка." });
        if (string.IsNullOrWhiteSpace(input.NewPassword) || input.NewPassword.Length < 8)
            return BadRequest(new { error = "Пароль должен быть не менее 8 символов." });

        var user = await _userMgr.FindByEmailAsync(input.Email.Trim());
        if (user is null || !user.IsActive)
            return BadRequest(new { error = "Ссылка недействительна или истекла." });

        var result = await _userMgr.ResetPasswordAsync(user, input.Token, input.NewPassword);
        if (!result.Succeeded)
        {
            // ASP.NET Identity отдаёт «InvalidToken» если токен испорчен/истёк
            // или если ResetPasswordAsync вызвали для другого юзера.
            return BadRequest(new
            {
                error = result.Errors.Any(e => e.Code == "InvalidToken")
                    ? "Ссылка недействительна или истекла. Запросите новую."
                    : string.Join("; ", result.Errors.Select(e => e.Description)),
            });
        }

        // Revoke все активные refresh/access токены — старые пароли больше нерелевантны.
        await _db.Database.ExecuteSqlRawAsync(
            "UPDATE \"OpenIddictTokens\" SET \"Status\" = 'revoked' WHERE \"Status\" = 'valid' AND \"Subject\" = {0}",
            new object[] { user.Id.ToString() });

        return Ok(new { ok = true });
    }

    private string BuildResetUrl(string email, string token)
    {
        // Frontend-host берём из Host заголовка через nginx — с учётом
        // что админка на admin.food-market.kz. Если запрос пришёл с
        // другого host (CORS-call) — фолбэк на admin-домен.
        var host = HttpContext?.Request?.Host.Value;
        var scheme = HttpContext?.Request?.Scheme ?? "https";
        if (string.IsNullOrEmpty(host) || host.Contains("localhost"))
        {
            return $"https://admin.food-market.kz/reset-password?email={HttpUtility.UrlEncode(email)}&token={HttpUtility.UrlEncode(token)}";
        }
        return $"{scheme}://{host}/reset-password?email={HttpUtility.UrlEncode(email)}&token={HttpUtility.UrlEncode(token)}";
    }

    private static bool CheckRateLimit(string ip)
    {
        var now = DateTime.UtcNow;
        var attempts = _ipAttempts.GetOrAdd(ip, _ => new List<DateTime>());
        lock (attempts)
        {
            // Чистим устаревшие.
            attempts.RemoveAll(t => now - t > _rateLimitWindow);
            if (attempts.Count >= _maxAttemptsPerWindow) return false;
            attempts.Add(now);
            return true;
        }
    }
}
