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;

[CloudScriptFunction("RequestPlayerContracts")]
public class RequestPlayerContracts : ICloudScriptFunction<FYGetPlayerContractsRequest, FYGetPlayerContractsResult>
{
    // Board slots are ordered Easy / Medium / Hard (same convention as ClaimActiveContract).
    private static readonly string[] Difficulties = ["Easy", "Medium", "Hard"];
    private const int RefreshHours = 12;

    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly UserDataService _userDataService;
    private readonly TitleDataService _titleDataService;
    private readonly Random _rnd = new();

    public RequestPlayerContracts(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
    {
        _httpContextAccessor = httpContextAccessor;
        _userDataService = userDataService;
        _titleDataService = titleDataService;
    }

    public async Task<FYGetPlayerContractsResult> ExecuteAsync(FYGetPlayerContractsRequest 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 userData = await _userDataService.FindAsync(
            userId, userId,
            new List<string>
            {
                "ContractsActive", "JobBoardsData",
                "FactionProgressionKorolev", "FactionProgressionICA", "FactionProgressionOsiris",
            }
        );

        var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value)!;
        var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value)!;

        var titleData = _titleDataService.Find(new List<string> { "Contracts", "Jobs", "LevelData" });
        var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"])!;
        var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"])!;
        var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"])!;

        var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        var lastRefresh = jobBoardsData.LastBoardRefreshTimeUtc?.Seconds ?? 0;

        // Initial fill only: populate boards that were seeded empty / never refreshed.
        // The periodic rotation is owned by RequestActiveObjectivesAndBoardsData, so we
        // don't refresh on a timer here to avoid two competing refresh policies.
        var boardsEmpty = jobBoardsData.Boards == null
            || jobBoardsData.Boards.Any(b => b.Contracts == null
                || b.Contracts.Any(c => string.IsNullOrEmpty(c.ContractId)));

        if (jobBoardsData.Boards != null && (lastRefresh == 0 || boardsEmpty))
        {
            foreach (var board in jobBoardsData.Boards)
            {
                if (board.Contracts == null || !jobs.TryGetValue(board.FactionId, out var factionJobs))
                {
                    continue;
                }

                var factionKey = "FactionProgression" + board.FactionId;
                var factionProgression = userData.TryGetValue(factionKey, out var fp)
                    ? JsonSerializer.Deserialize<int>(fp.Value)
                    : 0;

                var level = levelsData.TryGetValue(board.FactionId, out var levels)
                    ? levels.Length - Array.FindIndex(levels, xp => factionProgression >= xp)
                    : 0;

                for (var idx = 0; idx < board.Contracts.Count && idx < Difficulties.Length; idx++)
                {
                    if (!factionJobs.TryGetValue(Difficulties[idx], out var difficultyJobs))
                    {
                        continue;
                    }
                    var available = Array.FindAll(difficultyJobs,
                        jobId => contracts.TryGetValue(jobId, out var c) && level >= c.UnlockData.Level);
                    if (available.Length > 0)
                    {
                        board.Contracts[idx].ContractId = available[_rnd.Next(available.Length)];
                    }
                }
            }

            jobBoardsData.LastBoardRefreshTimeUtc = new FYTimestamp { Seconds = now };

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

        return new FYGetPlayerContractsResult
        {
            UserId = userId,
            Error = null,
            ActiveContracts = contractsActive.Contracts,
            FactionsContracts = new FYFactionsContractsData
            {
                Boards = jobBoardsData.Boards,
                LastBoardRefreshTimeUtc = jobBoardsData.LastBoardRefreshTimeUtc ?? new FYTimestamp { Seconds = now },
            },
            RefreshHours24UtcFromBackend = RefreshHours,
        };
    }
}
