using System.Text.Json;
using Prospect.Server.Api.Models.Data;
using Prospect.Server.Api.Services.Auth.Extensions;
using Prospect.Server.Api.Services.CloudScript.Models;
using Prospect.Server.Api.Services.CloudScript.Models.Data;
using Prospect.Server.Api.Services.UserData;

namespace Prospect.Server.Api.Services.CloudScript.Functions;

// Config d'une police d'assurance (TitleData "InventoryInsurance").
public class InventoryInsurancePolicy
{
    public string InsuranceId { get; set; }
    public double Cost { get; set; }
    public double Payout { get; set; }
}

[CloudScriptFunction("EnterMatchmakingStation")]
public class EnterMatchmakingStationFunction : ICloudScriptFunction<FYEnterMatchAzureFunction, FYEnterMatchmakingResult>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly UserDataService _userDataService;
    private readonly TitleDataService _titleDataService;
    private readonly ILogger<EnterMatchmakingStationFunction> _logger;

    public EnterMatchmakingStationFunction(
        IHttpContextAccessor httpContextAccessor,
        UserDataService userDataService,
        TitleDataService titleDataService,
        ILogger<EnterMatchmakingStationFunction> logger)
    {
        _httpContextAccessor = httpContextAccessor;
        _userDataService = userDataService;
        _titleDataService = titleDataService;
        _logger = logger;
    }

    public async Task<FYEnterMatchmakingResult> ExecuteAsync(FYEnterMatchAzureFunction request)
    {
        // Au déploiement, le client peut demander d'assurer une partie de l'inventaire.
        var insurance = request.PurchaseInventoryInsurances;
        var context = _httpContextAccessor.HttpContext;
        if (context != null && insurance?.ItemIds is { Count: > 0 })
        {
            try
            {
                await ApplyInsuranceAsync(context.User.FindAuthUserId(), insurance);
            }
            catch (Exception e)
            {
                // Ne jamais bloquer le déploiement à cause de l'assurance.
                _logger.LogWarning(e, "Failed to apply inventory insurance on deploy");
            }
        }

        return new FYEnterMatchmakingResult
        {
            Success = true,
            ErrorMessage = "",
            SingleplayerStation = true, // NOTE: This will always travel to single player station
            IsMatchTravel = false,
        };
    }

    // Marque les items choisis comme assurés (Insurance + propriétaire) et débite la
    // prime en InsuranceCurrency selon le coût de la police (TitleData InventoryInsurance).
    private async Task ApplyInsuranceAsync(string userId, FYPurchaseInventoryInsuranceRequest req)
    {
        var titleData = _titleDataService.Find(new List<string> { "InventoryInsurance" });
        var policies = JsonSerializer.Deserialize<Dictionary<string, InventoryInsurancePolicy>>(titleData["InventoryInsurance"]);
        var insuranceId = string.IsNullOrEmpty(req.InsuranceId) ? "Default" : req.InsuranceId;
        if (policies == null || !policies.TryGetValue(insuranceId, out var policy))
        {
            _logger.LogWarning("Unknown insurance policy {InsuranceId}", insuranceId);
            return;
        }

        var userData = await _userDataService.FindAsync(userId, userId, new List<string> { "Inventory", "Balance" });
        var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value);
        var balance = JsonSerializer.Deserialize<PlayerBalance>(userData["Balance"].Value);

        var toInsure = new HashSet<string>(req.ItemIds);
        var insuredCount = 0;
        foreach (var item in inventory)
        {
            if (!toInsure.Contains(item.ItemId)) continue;
            item.Insurance = insuranceId;
            item.InsuranceOwnerPlayfabId = userId;
            insuredCount++;
        }
        if (insuredCount == 0) return;

        // Prime = coût de la police × nombre d'items assurés (arrondi au supérieur),
        // plafonnée au solde disponible pour ne jamais passer en négatif.
        var premium = (int)Math.Ceiling(policy.Cost * insuredCount);
        premium = Math.Min(premium, balance.InsuranceCurrency);
        balance.InsuranceCurrency -= premium;

        await _userDataService.UpdateAsync(userId, userId, new Dictionary<string, string>
        {
            ["Inventory"] = JsonSerializer.Serialize(inventory),
            ["Balance"] = JsonSerializer.Serialize(balance),
        });
        _logger.LogInformation("Insured {Count} item(s) under policy {Policy} for user {User} (premium {Premium})",
            insuredCount, insuranceId, userId, premium);
    }
}
