using Core.Annotations;
using Core.Helpers;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Customization;
using Core.Models.Eft.Hideout;
using Core.Models.Eft.ItemEvent;
using Core.Models.Eft.Profile;
using Core.Models.Enums;
using Core.Routers;
using Core.Servers;
using Core.Services;
using Core.Utils.Cloners;
using ILogger = Core.Models.Utils.ILogger;
using Product = Core.Models.Eft.ItemEvent.Product;

namespace Core.Controllers;

[Injectable]
public class CustomizationController
{
    protected ILogger _logger;
    protected EventOutputHolder _eventOutputHolder;
    protected DatabaseService _databaseService;
    protected SaveServer _saveServer;
    protected LocalisationService _localisationService;
    protected ProfileHelper _profileHelper;
    protected ICloner _cloner;

    public CustomizationController
    (
        ILogger logger,
        EventOutputHolder eventOutputHolder,
        DatabaseService databaseService,
        SaveServer saveServer,
        LocalisationService localisationService,
        ProfileHelper profileHelper,
        ICloner cloner
    )
    {
        _logger = logger;
        _eventOutputHolder = eventOutputHolder;
        _databaseService = databaseService;
        _saveServer = saveServer;
        _localisationService = localisationService;
        _profileHelper = profileHelper;
        _cloner = cloner;
    }

    /// <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(string traderId, string 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)).ToList();
        matchingSuits = matchingSuits?.Where(s => clothing[s?.SuiteId].Properties.Side.Contains(pmcData?.Info?.Side)).ToList();

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

        return matchingSuits;
    }

    /// <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 BuyClothing(
        PmcData pmcData,
        BuyClothingRequestData buyClothingRequest,
        string sessionId)
    {
        var output = _eventOutputHolder.GetOutput(sessionId);

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

        var suitId = traderOffer?.SuiteId;
        if (OutfitAlreadyPurchased(suitId, sessionId))
        {
            var suitDetails = _databaseService.GetCustomization()[suitId];
            _logger.Error(_localisationService.GetText("customisation-item_already_purchased", new
            {
                ItemId = suitDetails?.Id,
                ItemName = suitDetails?.Name,
            }));
        }

        return output;
    }

    private bool OutfitAlreadyPurchased(object suitId, string sessionId)
    {
        return _saveServer.GetProfile(sessionId).Suits.Contains(suitId);
    }

    private Suit GetTraderClothingOffer(string sessionId, string? offerId)
    {
        var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s?.Id == offerId);
        if (foundSuit == null)
            throw new Exception(_localisationService.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>
    private void PayForClothingItems(string sessionId, PmcData pmcData, List<PaymentItemForClothing> itemsToPayForClothingWith,
        ItemEventRouterResponse output)
    {
        foreach (var inventoryItemToProcess in itemsToPayForClothingWith)
        {
            PayForClothingItem(sessionId, pmcData, inventoryItemToProcess, output);
        }
    }

    /// <summary>
    /// Update output object and player profile with purchase details for single piece of clothing
    /// </summary>
    /// <param name="sessionId">Session id</param>
    /// <param name="pmcData">Player profile</param>
    /// <param name="paymentItemDetails">Payment details</param>
    /// <param name="output">Client response</param>
    private void PayForClothingItem(string sessionId, PmcData pmcData, PaymentItemForClothing paymentItemDetails, ItemEventRouterResponse output)
    {
        var inventoryItem = pmcData?.Inventory?.Items?.FirstOrDefault(x => x?.Id == paymentItemDetails?.Id);
        if (inventoryItem == null)
        {
            _logger.Error(_localisationService.GetText("customisation-unable_to_find_clothing_item_in_inventory", paymentItemDetails.Id));
            return;
        }

        if (paymentItemDetails?.Del != null)
        {
            output?.ProfileChanges[sessionId]?.Items?.DeletedItems?.Add(new Product
            {
                Id = inventoryItem?.Id,
                Template = inventoryItem?.Template,
                ParentId = inventoryItem?.ParentId,
                SlotId = inventoryItem?.SlotId,
                Location = (ItemLocation)inventoryItem?.Location,
                Upd = inventoryItem?.Upd
            });

            pmcData?.Inventory?.Items?.Remove(inventoryItem);
        }

        if (inventoryItem?.Upd == null)
            inventoryItem.Upd = new() { StackObjectsCount = 1 };
        
        if (inventoryItem?.Upd?.StackObjectsCount == null)
            inventoryItem.Upd.StackObjectsCount = 1;

        if (inventoryItem?.Upd?.StackObjectsCount == paymentItemDetails?.Count)
        {
            output?.ProfileChanges[sessionId]?.Items?.DeletedItems?.Add(new Product
            {
                Id = inventoryItem?.Id,
                Template = inventoryItem?.Template,
                ParentId = inventoryItem?.ParentId,
                SlotId = inventoryItem?.SlotId,
                Location = (ItemLocation)inventoryItem?.Location,
                Upd = inventoryItem?.Upd
            });
            
            pmcData?.Inventory?.Items?.Remove(inventoryItem);
            return;
        }

        if (inventoryItem.Upd.StackObjectsCount > paymentItemDetails?.Count)
        {
            inventoryItem.Upd.StackObjectsCount -= paymentItemDetails?.Count;
            output?.ProfileChanges[sessionId]?.Items?.ChangedItems.Add(new Product
            {
                Id = inventoryItem?.Id,
                Template = inventoryItem?.Template,
                ParentId = inventoryItem?.ParentId,
                SlotId = inventoryItem?.SlotId,
                Location = (ItemLocation)inventoryItem?.Location,
                Upd = inventoryItem?.Upd
            });
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="sessionId"></param>
    /// <returns></returns>
    private List<Suit> GetAllTraderSuits(string sessionId)
    {
        var traders = _databaseService.GetTraders();
        var result = new List<Suit>();

        foreach (var trader in traders)
        {
            if (trader.Value.Base.CustomizationSeller.Value)
                result.AddRange(GetTraderSuits(trader.Key, sessionId));
        }
        
        return result;
    }

    /// <summary>
    /// Handle client/hideout/customization/offer/list
    /// </summary>
    /// <param name="sessionId"></param>
    /// <param name="info"></param>
    /// <returns></returns>
    public HideoutCustomisation GetHideoutCustomisation(string sessionId, EmptyRequestData info)
    {
        return _databaseService.GetHideout().Customisation;
    }

    /// <summary>
    /// Handle client/customization/storage
    /// </summary>
    /// <param name="sessionId"></param>
    /// <param name="info"></param>
    /// <returns></returns>
    public List<CustomisationStorage> GetCustomisationStorage(
        string sessionId,
        EmptyRequestData info)
    {
        var customisationResultsClone = _cloner.Clone(_databaseService.GetTemplates().CustomisationStorage);

        var profile = _profileHelper.GetFullProfile(sessionId);
        if (profile != null)
            return customisationResultsClone;
        
        customisationResultsClone.AddRange(profile.CustomisationUnlocks ?? new());

        return customisationResultsClone;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="profile"></param>
    /// <returns></returns>
    private string GetGameEdition(SptProfile profile)
    {
        var edition = profile?.CharacterData?.PmcData?.Info?.GameVersion;
        if (edition == null)
        {
            var launcherEdition = profile?.ProfileInfo?.Edition;
            switch (launcherEdition.ToLower())
            {
                case "edge of darkness":
                    return GameEditions.EDGE_OF_DARKNESS;
                case "unheard":
                    return GameEditions.UNHEARD;
                default:
                    return GameEditions.STANDARD;
            }
        }

        return edition;
    }

    /// <summary>
    /// Handle CustomizationSet event
    /// </summary>
    /// <param name="sessionId"></param>
    /// <param name="request"></param>
    /// <param name="pmcData"></param>
    /// <returns></returns>
    public ItemEventRouterResponse SetClothing(string sessionId, CustomizationSetRequest request, PmcData pmcData)
    {
        foreach (var customisation in request?.Customizations)
        {
            switch (customisation.Id)
            {
                case "dogTag":
                    pmcData.Customization.DogTag = customisation?.Id;
                    break;
                case "suite":
                    ApplyClothingItemToProfile(customisation, pmcData);
                    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="customization">Suit to apply to profile</param>
    /// <param name="pmcData">Profile to update</param>
    private void ApplyClothingItemToProfile(CustomizationSetOption customisation, PmcData pmcData)
    {
        var dbSuit = _databaseService.GetCustomization()[customisation?.Id];
        if (dbSuit == null)
        {
            _logger.Error($"Unable to find suit customisation id: {customisation?.Id}, cannot apply clothing to player profile: {pmcData.Id}");
            return;
        }

        if (dbSuit.Parent == "5cd944d01388ce000a659df9")
        {
            pmcData.Customization.Body = dbSuit?.Properties?.Body;
            pmcData.Customization.Hands = dbSuit?.Properties?.Hands;

            return;
        }

        if (dbSuit.Parent == "5cd944ca1388ce03a44dc2a4")
        {
            pmcData.Customization.Feet = dbSuit?.Properties?.Feet;
        }
    }
}
