using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.SignalR;
using Prospect.Server.Api.Hubs;
using Prospect.Server.Api.Services.Auth.Extensions;
using Prospect.Server.Api.Services.CloudScript.Models;
using Prospect.Server.Api.Services.UserData;

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

public class OnSquadMatchmakingSuccessMessage {
    [JsonPropertyName("success")]
    public bool Success { get; set; }
    [JsonPropertyName("sessionId")]
    public string SessionID { get; set; }
    [JsonPropertyName("squadId")]
    public string SquadID { get; set; }
}

[CloudScriptFunction("EnterMatchmakingMatch")]
public class EnterMatchmakingMatchFunction : ICloudScriptFunction<FYEnterMatchAzureFunction, FYEnterMatchmakingResult>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IHubContext<CycleHub> _hubContext;
    private readonly UserDataService _userDataService;
    private readonly TitleDataService _titleDataService;
    private readonly SignalRConnectionRegistry _registry;

    public EnterMatchmakingMatchFunction(IHubContext<CycleHub> hubContext, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService, SignalRConnectionRegistry registry)
    {
        _httpContextAccessor = httpContextAccessor;
        _hubContext = hubContext;
        _userDataService = userDataService;
        _titleDataService = titleDataService;
        _registry = registry;
    }

    public async Task<FYEnterMatchmakingResult> ExecuteAsync(FYEnterMatchAzureFunction request)
    {
        var context = _httpContextAccessor.HttpContext;
        if (context == null)
        {
            throw new CloudScriptException("CloudScript was not called within a http request");
        }

        var userId = context.User.FindAuthUserId();
        var titleData = _titleDataService.Find(new List<string>{"Contracts"});
        var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"]);

        var userData = await _userDataService.FindAsync(
            userId, userId,
            new List<string>{"ContractsActive", "Inventory"}
        );
        var inventory = JsonSerializer.Deserialize<List<FYCustomItemInfo>>(userData["Inventory"].Value);
        var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value);
        // Compute delivery quest progress before deploy.
        // This ensures that the quest progress will be shown on the planet.
        // The station doesn't seem to care about delivery quests progress and calculates progress
        // based on actual items in stash. And so does the "ClaimActiveContract" function.
        foreach (var contractActive in contractsActive.Contracts) {
            if (!contracts.TryGetValue(contractActive.ContractID, out var contract)) {
                continue;
            }
            for (var i = 0; i < contract.Objectives.Length; i++) {
                if (i >= contractActive.Progress.Length) {
                    // Defensive: player progress array shorter than the objective list.
                    break;
                }
                var objective = contract.Objectives[i];
                // Raid-runtime objectives (kills, dead-drops, visited areas, looted
                // containers) are simulated inside the client-hosted raid; with no
                // dedicated game server their progress is never reported to the backend,
                // so they would never be saved. Auto-credit them on deploy so the
                // objective persists and the station shows the contract as completable.
                if (objective.Type.IsRaidRuntime()) {
                    contractActive.Progress[i] = objective.MaxProgress;
                    continue;
                }
                if (objective.Type != EYContractObjectiveType.OwnNumOfItem) {
                    continue;
                }
                int remaining = objective.MaxProgress;
                foreach (var item in inventory) {
                    if (item.BaseItemId != objective.ItemToOwn) {
                        continue;
                    }
                    remaining -= item.Amount;
                    if (remaining <= 0) {
                        remaining = 0;
                        break;
                    }
                }
                contractActive.Progress[i] = objective.MaxProgress - remaining;
            }
        }

        await _userDataService.UpdateAsync(
            userId, userId,
            new Dictionary<string, string>{
                ["ContractsActive"] = JsonSerializer.Serialize(contractsActive),
            }
        );

        // Send the "matchmaking succeeded / travel to match" signal only to the deploying
        // player (via their mapped SignalR connection) — broadcasting to Clients.All made
        // every connected player travel too. Fall back to broadcast only if the connection
        // isn't mapped yet.
        // Dedicated-server mode (2nd preprod): point the client at the real game server address
        // to travel to; otherwise keep the map name (client-hosted). Same image — env flips it.
        var gameServer = Environment.GetEnvironmentVariable("GAMESERVER_ADDRESS");
        var deployMessage = new OnSquadMatchmakingSuccessMessage {
            Success = true,
            SessionID = string.IsNullOrEmpty(gameServer) ? request.MapName : gameServer,
            SquadID = request.SquadId,
        };
        var connectionId = _registry.GetConnection(userId);
        if (connectionId != null)
            await _hubContext.Clients.Client(connectionId).SendAsync("OnSquadMatchmakingSuccess", deployMessage);
        else
            await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", deployMessage);

        return new FYEnterMatchmakingResult
        {
            Success = true,
            ErrorMessage = "",
            SingleplayerStation = false,
            NumAttempts = 1,
            Blocker = 0,
            IsMatchTravel = true,
            SessionId = "", // TODO: Not sure how this affects match travel
        };
    }
}