using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Customization;
using SPTarkov.Server.Core.Models.Eft.Hideout;
using SPTarkov.Server.Core.Models.Eft.ItemEvent;
using SPTarkov.Server.Core.Models.Eft.Trade;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Routers;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Cloners;
using Customization = SPTarkov.Server.Core.Models.Eft.Common.Tables.Customization;

namespace SPTarkov.Server.Core.Controllers;

[Injectable]
public class CustomizationController(
    ISptLogger<CustomizationController> logger,
    EventOutputHolder eventOutputHolder,
    DatabaseService databaseService,
    SaveServer saveServer,
    ServerLocalisationService serverLocalisationService,
    ProfileHelper profileHelper,
    ICloner cloner,
    PaymentService paymentService
)
{
    /// <summary>
    ///     Get purchasable clothing items from trader that match players side (usec/bear)
    /// </summary>
    /// <param name="traderId">trader to look up clothing for</param>
    /// <param name="sessionId">Session id</param>
    /// <returns>Suit array</returns>
    public List<Suit> GetTraderSuits(MongoId traderId, MongoId sessionId)
    {
        var pmcData = profileHelper.GetPmcProfile(sessionId);
        var clothing = databaseService.GetCustomization();
        var suits = databaseService.GetTrader(traderId)?.Suits;

        var matchingSuits = suits?.Where(s => clothing.ContainsKey(s.SuiteId));
        matchingSuits = matchingSuits?.Where(s =>
            clothing[s.SuiteId]?.Properties?.Side?.Contains(pmcData?.Info?.Side ?? string.Empty) ?? false
        );

        if (matchingSuits == null)
        {
            throw new Exception(serverLocalisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
        }

        return matchingSuits.ToList();
    }

    /// <summary>
    ///     Handle CustomizationBuy event
    ///     Purchase/unlock a clothing item from a trader
    /// </summary>
    /// <param name="pmcData">Player profile</param>
    /// <param name="buyClothingRequest">Request object</param>
    /// <param name="sessionId">Session id</param>
    /// <returns>ItemEventRouterResponse</returns>
    public ItemEventRouterResponse BuyCustomisation(PmcData pmcData, BuyClothingRequestData buyClothingRequest, MongoId sessionId)
    {
        var output = eventOutputHolder.GetOutput(sessionId);

        var traderOffer = GetTraderClothingOffer(sessionId, buyClothingRequest.Offer);
        if (traderOffer is null)
        {
            logger.Error(serverLocalisationService.GetText("customisation-unable_to_find_suit_by_id", buyClothingRequest.Offer));
            return output;
        }

        var suitId = traderOffer.SuiteId;
        if (OutfitAlreadyPurchased(traderOffer.SuiteId, sessionId))
        {
            var suitDetails = databaseService.GetCustomization().GetValueOrDefault(suitId);
            logger.Error(
                serverLocalisationService.GetText(
                    "customisation-item_already_purchased",
                    new { itemId = suitDetails?.Id, itemName = suitDetails?.Name }
                )
            );

            return output;
        }

        // Charge player for buying item
        PayForClothingItems(sessionId, pmcData, buyClothingRequest.Items, output);

        var profile = saveServer.GetProfile(sessionId);

        // TODO: Merge with function _profileHelper.addHideoutCustomisationUnlock
        var rewardToStore = new CustomisationStorage
        {
            Id = suitId,
            Source = CustomisationSource.UNLOCKED_IN_GAME,
            Type = CustomisationType.SUITE,
        };

        profile.CustomisationUnlocks?.Add(rewardToStore);

        return output;
    }

    /// <summary>
    ///     Has an outfit been purchased by a player
    /// </summary>
    /// <param name="suitId">clothing id</param>
    /// <param name="sessionId">Session id of profile to check for clothing in</param>
    /// <returns>true if already purchased</returns>
    protected bool OutfitAlreadyPurchased(MongoId suitId, MongoId sessionId)
    {
        var fullProfile = profileHelper.GetFullProfile(sessionId);

        // Check if clothing can be found by id
        return fullProfile.CustomisationUnlocks?.Exists(customisation => Equals(customisation.Id, suitId)) ?? false;
    }

    /// <summary>
    ///     Get clothing offer from trader by suit id
    /// </summary>
    /// <param name="sessionId">Session/Player id</param>
    /// <param name="offerId"></param>
    /// <returns>Suit</returns>
    protected Suit? GetTraderClothingOffer(MongoId sessionId, MongoId offerId)
    {
        var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId);
        if (foundSuit is null)
        {
            logger.Error(serverLocalisationService.GetText("customisation-unable_to_find_suit_with_id", offerId));
        }

        return foundSuit;
    }

    /// <summary>
    ///     Update output object and player profile with purchase details
    /// </summary>
    /// <param name="sessionId">Session id</param>
    /// <param name="pmcData">Player profile</param>
    /// <param name="itemsToPayForClothingWith">Clothing purchased</param>
    /// <param name="output">Client response</param>
    protected void PayForClothingItems(
        MongoId sessionId,
        PmcData pmcData,
        List<PaymentItemForClothing>? itemsToPayForClothingWith,
        ItemEventRouterResponse output
    )
    {
        if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0)
        {
            return;
        }

        foreach (var inventoryItemToProcess in itemsToPayForClothingWith)
        {
            var options = new ProcessBuyTradeRequestData
            {
                SchemeItems = [new IdWithCount { Count = inventoryItemToProcess.Count!.Value, Id = inventoryItemToProcess.Id }],
                TransactionId = Traders.RAGMAN,
                Action = "BuyCustomization",
                Type = string.Empty,
                ItemId = MongoId.Empty(),
                Count = 0,
                SchemeId = 0,
            };

            paymentService.PayMoney(pmcData, options, sessionId, output);
        }
    }

    /// <summary>
    ///     Get all suits from Traders
    /// </summary>
    /// <param name="sessionId">Session/Player id</param>
    /// <returns></returns>
    protected IEnumerable<Suit> GetAllTraderSuits(MongoId sessionId)
    {
        return databaseService
            .GetTraders()
            .Where(trader => trader.Value.Base.CustomizationSeller.GetValueOrDefault(false))
            .SelectMany(trader => GetTraderSuits(trader.Key, sessionId));
    }

    /// <summary>
    ///     Handle client/hideout/customization/offer/list
    /// </summary>
    /// <returns>Hideout customizations</returns>
    public HideoutCustomisation GetHideoutCustomisation()
    {
        return databaseService.GetHideout().Customisation;
    }

    /// <summary>
    ///     Handle client/customization/storage
    /// </summary>
    /// <param name="sessionId">Session/Player id</param>
    /// <returns></returns>
    public List<CustomisationStorage> GetCustomisationStorage(MongoId sessionId)
    {
        var customisationResultsClone = cloner.Clone(databaseService.GetTemplates().CustomisationStorage);

        var profile = profileHelper.GetFullProfile(sessionId);

        customisationResultsClone!.AddRange(profile.CustomisationUnlocks ?? []);

        return customisationResultsClone;
    }

    /// <summary>
    ///     Handle CustomizationSet event
    /// </summary>
    /// <param name="sessionId">Session/Player id</param>
    /// <param name="request"></param>
    /// <param name="pmcData">Players PMC profile</param>
    /// <returns>ItemEventRouterResponse</returns>
    public ItemEventRouterResponse SetCustomisation(MongoId sessionId, CustomizationSetRequest request, PmcData pmcData)
    {
        foreach (var customisation in request.Customizations!)
        {
            switch (customisation.Type)
            {
                case "dogTag":
                    pmcData.Customization!.DogTag = customisation.Id;
                    break;
                case "suite":
                    ApplyClothingItemToProfile(customisation, pmcData);
                    break;
                case "voice":
                    pmcData.Customization.Voice = customisation.Id;
                    break;
                default:
                    logger.Error($"Unhandled customisation type: {customisation.Type}");
                    break;
            }
        }

        return eventOutputHolder.GetOutput(sessionId);
    }

    /// <summary>
    ///     Applies a purchased suit to the players doll
    /// </summary>
    /// <param name="customisation">Suit to apply to profile</param>
    /// <param name="pmcData">Profile to update</param>
    protected void ApplyClothingItemToProfile(CustomizationSetOption customisation, PmcData pmcData)
    {
        if (!databaseService.GetCustomization().TryGetValue(customisation.Id, out var dbSuit))
        {
            logger.Error(
                $"Unable to find suit customisation id: {customisation.Id}, cannot apply clothing to player profile: {pmcData.Id}"
            );
            return;
        }

        pmcData.Customization ??= new Customization();

        switch (dbSuit?.Parent)
        {
            // Body
            case CustomisationTypeId.UPPER:
                pmcData.Customization.Body = dbSuit.Properties.Body;
                pmcData.Customization.Hands = dbSuit.Properties.Hands;
                return;
            // Feet
            case CustomisationTypeId.LOWER:
                pmcData.Customization.Feet = dbSuit.Properties.Feet;
                break;
        }
    }
}
