using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using Random = UnityEngine.Random;

public enum ShopState
{
    Open,
    Busy,
    Close,
}

public class Market : SingletonMonoBehaviour<Market>
{ 
    public static readonly int ShopStockCount = 20;
    private static readonly int SpecialOrderCount = 5;
    private static readonly (int want, float weight)[] CustomerWeightTable = {
        (1, 75f),
        (2, 15f),
        (3, 3.5f),
        (4, 1.25f),
        (5, .25f),
        (0, 5f),
    };
    private static readonly float walkerSpecialRate = .01f;
    private static readonly float customerSpecialRate = .01f;

    [SerializeField] private CustomerFlow customerFlow;
    [SerializeField] private GameObject orderPosisionObject;
    [SerializeField] private CustomerData customerData;
    [SerializeField] private CustomerController customerControllerPrefab;

    public List<ProductStockData> DisplayFlavors => displayFlavors;
    private List<ProductStockData> displayFlavors = new List<ProductStockData>();
    public List<int> ShuffledOrder => shuffledOrder;
    private List<int> shuffledOrder = new List<int>();
    
    public IReadOnlyReactiveProperty<ShopState> CurrentShopState => shopState;
    private readonly ReactiveProperty<ShopState> shopState = new ReactiveProperty<ShopState>();
    public IReadOnlyReactiveCollection<CustomerController> CustomerControllerList => customerControllerList;
    private readonly ReactiveCollection<CustomerController> customerControllerList = new ReactiveCollection<CustomerController>();
    public IReadOnlyReactiveCollection<CustomerController> CustomerList => customerList;
    private readonly ReactiveCollection<CustomerController> customerList = new ReactiveCollection<CustomerController>();
    private readonly Subject<CustomerController> requestSubject = new Subject<CustomerController>();
    private readonly ReactiveCollection<CustomerController> waitCustomerList = new ReactiveCollection<CustomerController>();
    private readonly Subject<Unit> orderSubject = new Subject<Unit>();
    public readonly BoolReactiveProperty IsPause = new BoolReactiveProperty(false);
    public IReadOnlyReactiveProperty<bool> IsPromotion => isPromotion; 
    private readonly BoolReactiveProperty isPromotion = new BoolReactiveProperty();
    
    public Subject<int> SellObservable => sellObservable;
    private readonly Subject<int> sellObservable = new Subject<int>();
    public Subject<List<int>> SellOrderSubject => sellOrderSubject;
    private readonly Subject<List<int>> sellOrderSubject = new Subject<List<int>>();
    public Subject<(bool isReorder, List<int> refillList)> RefillSubject => refillSubject;
    private readonly Subject<(bool isReorder, List<int> refillList)> refillSubject = new Subject<(bool, List<int>)>();
    
    private int orderIndex;
    private int oneByOneIndex = 0;
    private GameData gameData;
    
    // Start is called before the first frame update
    void Start()
    {
        shopState.AddTo(this);
        customerList.AddTo(this);
        waitCustomerList.AddTo(this);
        requestSubject.AddTo(this);
        orderSubject.AddTo(this);
        IsPause.AddTo(this);
        isPromotion.AddTo(this);
        
        gameData = GameDataManager.GameData;

#if UNITY_EDITOR || DEVELOPMENT_BUILD
        CheckAndFixStock();
        StockFlavorLog();
#endif
        
        // 陳列/売り順決定
        ReShuffle(gameData.ShopStock);

        Observable.Interval(TimeSpan.FromSeconds(1f))
            .Where(_ => shuffledOrder.Count == 0)
            .Where(_ => gameData.ShopStock.Count > 0)
            .Subscribe(_ =>
            {
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                Debug.Log("displayFlavors updated");
#endif
                ReShuffle(gameData.ShopStock);
                shopState.Value = ShopState.Open;
            });
        
        // お店の状態設定
        CheckStock(gameData.ShopStock);
        
        // 購入リクエスト
        var maxOrder = orderPosisionObject.transform.childCount;
        Observable.Merge(
                orderSubject /*.Do(_ => { Debug.Log($"aa orderSubject");})*/,
                waitCustomerList.ObserveAdd().AsUnitObservable() /*.Do(_ => { Debug.Log($"aa waitCustomerListAdd");})*/,
                shopState.Where(x => x == ShopState.Open).AsUnitObservable() /*.Do(_ => { Debug.Log($"aa shopStateOpen");})*/
            )
            .BatchFrame()
            .Where(_ => waitCustomerList.Count > 0)
            .SelectMany(_ => waitCustomerList.ToList().GetRange(0, Mathf.Min(maxOrder, waitCustomerList.Count)))
            .Subscribe(customer =>
            {
                var count = customerList.Count(x => x.State.Value == CustomerState.Order);
                if (count >= maxOrder)
                {
                    // shopState.Value = ShopState.Busy;
                    return;
                }
                waitCustomerList.Remove(customer);
                // 購入へ
                orderIndex++;
                if (orderIndex >= maxOrder)
                {
                    orderIndex = 0;
                }
                customer.SetOrderPosition(orderIndex);
                customer.ChangeCustomerState(CustomerState.Order);
            }).AddTo(this);

        requestSubject.BatchFrame().Subscribe(customers =>
        {
            // 来客数カウント
            gameData.AddCustomerCount(customers.Count);
            
            var orders = new List<int>();
            var dontBuyCustomerList = new List<CustomerController>();
            foreach (var controller in customers)
            {
                // 何も買わない
                if (displayFlavors.Count == 0 || controller.OrderCount == 0)
                {
                    controller.CallWaitForSeconds(1.5f, () =>
                    {
                        controller.ChangeCustomerState(CustomerState.Leave);
                    });
                    dontBuyCustomerList.Add(controller);
                    continue;
                }
                
                // 売り切れ
                if (shuffledOrder.Count == 0)
                {
                    controller.SetWantFlavor(displayFlavors.RandomChoose());
                    controller.CallWaitForSeconds(1.5f, () =>
                    {
                        controller.SetComplain();
                        controller.ChangeCustomerState(CustomerState.SadLeave);
                    });
                    dontBuyCustomerList.Add(controller);
                    continue;
                }

                // shuffledOrder順に販売
                var tmpOrderCount = Mathf.Min(controller.OrderCount, shuffledOrder.Count);
                var tmpOrders = shuffledOrder.GetRange(0, tmpOrderCount);
                shuffledOrder.RemoveRange(0, tmpOrderCount);
                orders.AddRange(tmpOrders);

                // コーンの味吹き出しを設定
                try
                {
                    controller.SetWantFlavor(displayFlavors[tmpOrders.RandomChoose()]);
                }
                catch (Exception e)
                {
                    Debug.LogError($"d:{displayFlavors.Count}, {string.Join("", tmpOrders)}" +
                                   $"\nshuffled:{shuffledOrder.Count}, maxNum:{shuffledOrder.Max()}");
                    /*
                     * 例外握りつぶし
                     * 存在しないorderを引いたのでそのまま処理せず逃がす
                     */
                    controller.CallWaitForSeconds(1.5f, () =>
                    {
                        controller.ChangeCustomerState(CustomerState.Leave);
                    });
                    dontBuyCustomerList.Add(controller);
                }
            }
            foreach (var customerController in dontBuyCustomerList)
            {
                customers.Remove(customerController);
            }
            if (gameData.ShopStock.Count == 0)
            {
                shopState.Value = ShopState.Close;
            }
            if (orders.Count == 0)
            {
                return;
            }
            
            // 購入
            var flavors = orders.Select(x => displayFlavors[x]).ToList();
            var coin = SellPopcorn(flavors);
            
            // 獲得処理
            gameData.Coin += coin;
            gameData.Heart += customers.Count;
            GameDataManager.SaveGameData();
            
            // 商品補充
            RefillShopStockData();
            
            // 表示データ更新
            var isReorder = false;
            var refillList = new List<int>();
            if (gameData.ShopStock.Count > shuffledOrder.Count)
            {
                refillList = RefillDisplayFlavors(gameData.ShopStock);
            }
            else
            {
                isReorder = CheckRemaining(gameData.ShopStock.Count);
                if (isReorder)
                {
                    ReShuffle(gameData.ShopStock);
                }
            }

            this.CallWaitForSeconds(1.5f, () =>
            {
                sellObservable.OnNext(coin);
                sellOrderSubject.OnNext(orders);
                CheckStock(gameData.ShopStock);

                this.CallWaitForSeconds(1f, () =>
                {
                    refillSubject.OnNext((isReorder, refillList));
                    foreach (var controller in customers)
                    {
                        controller.SetPurchased();
                        controller.ChangeCustomerState(CustomerState.EatingLeave);
                    }
                });
            });
        }).AddTo(this);
        
        // 購入客
        customerList.ObserveAdd()
            .Select(x => x.Value)
            .Subscribe(controller =>
            {
                if (shopState.Value == ShopState.Close)
                {
                    controller.ChangeCustomerState(CustomerState.Leave);
                    return;
                }
                
                // 売り切れの場合帰る
                shopState
                    .Where(x => x == ShopState.Close && controller.State.Value != CustomerState.Order)
                    .Subscribe(x =>
                    {
                        controller.ChangeCustomerState(CustomerState.Leave);
                    }).AddTo(controller);
                
                controller.State
                    .Where(x => x != CustomerState.Order)
                    .Subscribe(c =>
                    {
                        orderSubject.OnNext(default);
                    }).AddTo(controller);
                
                // 店に向かう
                controller.ChangeCustomerState(CustomerState.Wait);
            }).AddTo(this);

        // 客湧き
        customerFlow.Flow.Subscribe(isCustomer =>
        {
            if (IsPause.Value)
            {
                return;
            }

            if (gameData.isFirstPlay)
            {
                return;
            }
            // 一般客orセレブ
            var (isSpecial, orderCount) = GetCustomerData(isCustomer);
            
            // 複数パターンある場合ChooseRandom
            var prefab = isSpecial ? customerData.ChooseSpecialPrefab() : customerData.ChooseNormalPrefab();
            var customerController = SpawnCustomer();
            customerController.Setup(orderPosisionObject.transform.GetComponentsInChildren<Transform>().ToList().Skip(1).ToList());
            customerController.OrderCount = orderCount;
            customerController.CustomerPrefab = prefab;
            customerControllerList.Add(customerController);
 
            
            if (isCustomer)
            {
                // 近くまで歩く(タップされたらcustomerList.Add()
                customerController.ChangeCustomerState(CustomerState.WalkShop);
                customerController.TappedObservable
                    .TakeUntil(customerController.MoveEndObservable.Where(type => type == CustomerMovingType.WalkCenter))
                    .Take(1)
                    .Subscribe(_ =>
                    {
                        customerList.Add(customerController);
                    }).AddTo(customerController);
            }
            else
            {
                // 歩行者はタップ後customerList.Add()
                customerController.ChangeCustomerState(CustomerState.Walk);
                customerController.TappedObservable
                    .Take(1)
                    .Subscribe(_ =>
                    {
                        customerList.Add(customerController);
                    }).AddTo(customerController);
            }
        }).AddTo(this);
        
        // お客さんが少なくなったら弟が走る
        CustomerList.ObserveCountChanged(true).AsUnitObservable()
            .Merge(Observable.Interval(TimeSpan.FromSeconds(1f)).AsUnitObservable())
            .Where(_ => CurrentShopState.Value != ShopState.Close)
            .Subscribe(_ =>
            {
                var count = CustomerList.Count(c => c.State.Value == CustomerState.Wait || c.State.Value == CustomerState.Order);
                if (count < BrotherPinkView.Instance.FewerBorder)
                {
                    BrotherPinkView.Instance.SetWalk();
                }
            }).AddTo(this);
    }


    public static (bool isSpecial, int orderCount) GetCustomerData (bool isCustomer)
    {
        var specialRate = isCustomer ? walkerSpecialRate : customerSpecialRate;
        var isSpecial = Random.value < specialRate;

        // セレブは5個購入固定
        return isSpecial ? (true, specialOrderCount: SpecialOrderCount) : (false, GetOrderCount());
    }
    
    // お客さん出現パターン確率計算と行動パターン計算
    private static int GetOrderCount()
    {
        var randomPoint = Random.value * CustomerWeightTable.Sum(x => x.weight);
        foreach (var value in CustomerWeightTable)
        {
            if (randomPoint < value.weight)
            { 
                return value.want;
            }
            randomPoint -= value.weight;
        }
        return CustomerWeightTable.Last().want;
    }

    private CustomerController SpawnCustomer()
    {
        var customerController = Instantiate(customerControllerPrefab, transform);
        IsPause.Subscribe(x => customerController.IsPause = x).AddTo(customerController);
        
        customerController.MoveEndObservable
            .SkipLatestValueOnSubscribe()
            .DistinctUntilChanged()
            .Subscribe(prevMovingType =>
            {
                // Debug.Log($"move end {type} {customerController.GetHashCode()}");
                switch (prevMovingType)
                {
                    case CustomerMovingType.WalkSide:
                    case CustomerMovingType.WalkSideEat:
                        customerList.Remove(customerController);
                        customerControllerList.Remove(customerController);
                        Destroy(customerController.gameObject);
                        break;
                    case CustomerMovingType.WalkCenter:
                        if (customerController.State.Value == CustomerState.WalkShop)
                        {
                            customerList.Add(customerController);
                        }
                        break;
                    case CustomerMovingType.WalkBackHalf:
                        waitCustomerList.Add(customerController);
                        break;
                    case CustomerMovingType.StayBackOrder:
                        requestSubject.OnNext(customerController);
                        break;
                    default:
                        break;
                }
            }).AddTo(customerController);
        return customerController;
    }

    private int SellPopcorn(List<ProductStockData> stockDataList)
    {
        var gameData = GameDataManager.GameData;
        // 品切れ
        if (gameData.ShopStock.Count == 0)
        {
            return 0;
        }

        // フレーバーを売る
        var recipeList = SpreadsheetDataManager.Instance.GetBaseDataList<ProductData>(Const.ProductDataSheet);
        var rarityList = SpreadsheetDataManager.Instance.GetBaseDataList<RarityData>(Const.RarityDataSheet);
        var coin = 0;
        foreach (var stockData in stockDataList)
        {
            var productData = recipeList.First(data => data.id == stockData.FlavorId);
            var rarityData = rarityList.First(data => data.Rarity == stockData.Rarity);
            var targetIndex = gameData.ShopStock.FindIndex(data => data.FlavorId == stockData.FlavorId && data.Rarity == stockData.Rarity);
            gameData.ShopStock.RemoveAt(targetIndex);
            gameData.AddSalesCount(stockData.FlavorId, 1, stockData.Rarity);
            coin += productData.GetRarityPrice(rarityData.Rarity);
        }
        return coin;
    }

    private List<int> ShuffleOrder(int length)
    {
        return Enumerable.Range(0, length)
            .OrderBy(_ => Random.value).ToList();
    }

    private void RefillShopStockData()
    {
        // 手前のタンクから出し多分stockをへらす
        // へらした分を店頭リストに追加する
        var gameData = GameDataManager.GameData;
        var shopSpace = ShopStockCount - gameData.ShopStock.Count;
        switch (gameData.RefillMode)
        {
            case ProductRefillMode.OneByOne:
                while (shopSpace > 0 && gameData.StorageTanks.Sum(data => data.Stock) > 0)
                {
                    var tanks = gameData.StorageTanks.Where(tank => !tank.IsEmpty).ToList();
                    if (oneByOneIndex >= tanks.Count)
                    {
                        oneByOneIndex = 0;
                    }
                    var stockList = tanks[oneByOneIndex].GetStock(1);
                    gameData.ShopStock.AddRange(stockList);
                    shopSpace -= stockList.Count;
                    oneByOneIndex++;
                }
                break;
            case ProductRefillMode.TankByTank:
                foreach (var tank in gameData.StorageTanks.Where(tank => !tank.IsEmpty))
                {
                    if (shopSpace == 0)
                    {
                        break;
                    }
                    var stockList = tank.GetStock(shopSpace);
                    gameData.ShopStock.AddRange(stockList);
                    shopSpace -= stockList.Count;
                }
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        GameDataManager.SaveGameData();
    }

    public List<int> RefillDisplayFlavors(List<ProductStockData> shopStock)
    {
        // 補充された場合店頭のフレーバー入れ替え
        var refillCount = shopStock.Count - shuffledOrder.Count;
        if (refillCount <= 0)
        {
            return new List<int>();
        }
        // 補充候補リスト
        var orders = Enumerable.Range(0, shopStock.Count).Except(shuffledOrder).ToList();
        var refillList = orders.GetRange(0, refillCount).OrderBy(_ => Random.value).ToList();
        shuffledOrder.AddRange(refillList);
        try
        {
            var diffDisplayCount = shopStock.Count - displayFlavors.Count;
            if (diffDisplayCount > 0)
            {
                displayFlavors.AddRange(shopStock.GetRange(displayFlavors.Count, diffDisplayCount));
            }
            for (int i = 0; i < refillList.Count; i++)
            {
                displayFlavors[refillList[i]] = shopStock[shopStock.Count - 1 - i];
            }
        }
        catch (Exception e)
        {
            throw;
        }
        return refillList;
    }

    private bool CheckRemaining(int currentShopStockCount)
    {
        if (currentShopStockCount <= 13 && shuffledOrder.Exists(x => x > 13))
        {
            return true;
        }
        if (currentShopStockCount <= 7 && shuffledOrder.Exists(x => x > 7))
        {
            return true;
        }

        return false;
    }

    public void ReShuffle(List<ProductStockData> shopStock)
    {
        displayFlavors = shopStock.ToList();
        shuffledOrder = ShuffleOrder(displayFlavors.Count);
    }

    public void CheckStock(List<ProductStockData> shopStock)
    {
        shopState.Value = shopStock.Count == 0 ? ShopState.Close : ShopState.Open;
    }

    public void AdClickAction()
    {
        isPromotion.Value = true;
        customerFlow.StartAdWalker(() =>
        {
            isPromotion.Value = false;    
            BrotherPinkView.Instance.StopPromotion();
        });
    }

    public CustomerController SetTutorialCustomer()
    {
        var customerController = SpawnCustomer();
        customerController.SetupCenter(orderPosisionObject.transform.GetComponentsInChildren<Transform>().ToList().Skip(1).ToList());
        customerController.OrderCount = 1;
        customerController.CustomerPrefab = customerData.ChooseNormalPrefab();
        customerControllerList.Add(customerController);
        
        // タップ後customerList.Add()
        customerController.ChangeCustomerState(CustomerState.CenterStop);
        customerController.TappedObservable
            .Take(1)
            .Subscribe(_ =>
            {
                customerList.Add(customerController);
            }).AddTo(customerController);
        return customerController;
    }

    private void CheckAndFixStock()
    {
        if (GameDataManager.GameData.ShopStock.Count > ShopStockCount)
        {
            GameDataManager.GameData.ShopStock.RemoveRange(0, GameDataManager.GameData.ShopStock.Count - ShopStockCount);
        }
    }

    public void SetGameData(GameData newGameData)
    {
        gameData = newGameData;
    }

    public static void StockFlavorLog()
    {
        var shopStockString = "";
        foreach (var data in RecipeData.GetAllRecipe())
        {
            var shopStockCount = GameDataManager.GameData.ShopStock.FindAll(x => x.FlavorId == data.RecipeId).Count;
            var tank = GameDataManager.GameData.StorageTanks.FindAll(x => x.FlavorId == data.RecipeId).Sum(x => x.Stock);
            if (shopStockCount + tank == 0)
            {
                continue;
            }
            shopStockString += $"{data.Name} shop:{shopStockCount} tank:{tank}\n";
        }
        Debug.Log(shopStockString);
    }
}
