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 : MonoBehaviour
{
    public static readonly int ShopStockCount = 20;

    [SerializeField] private ShopStockView stockView;
    [SerializeField] private MarketCartView cartView;
    [SerializeField] private BrotherBlueView blueView;
    [SerializeField] private BrotherPinkView pinkView;
    [SerializeField] private GameObject closeSign;
    [SerializeField] private GameObject customerPrefab;
    [SerializeField] private GameObject orderPosisionObject;
    [SerializeField] private Transform coinPrefab;
    [SerializeField] private Transform rootTransform;

    private List<int> displayFlavors;
    private List<int> shuffledOrder;
    private ReactiveProperty<ShopState> shopState = new ReactiveProperty<ShopState>();
    private ReactiveCollection<CustomerController> waitCustomerList = new ReactiveCollection<CustomerController>();
    private Subject<CustomerController> requestSubject = new Subject<CustomerController>();
    private Subject<Unit> orderSubject = new Subject<Unit>();

    private int orderIndex;

    // Start is called before the first frame update
    void Start()
    {
        shopState.AddTo(this);
        waitCustomerList.AddTo(this);
        requestSubject.AddTo(this);
        orderSubject.AddTo(this);
        
        var gameData = GameDataManager.GameData;
        CoinManager.Instance.ChangeCoin(gameData.Coin);
        HeartMeter.Instance.Initialize();
        HeartMeter.Instance.SetHeart(gameData.Heart);
        
        var shopLevelList = SpreadsheetDataManager.Instance.GetBaseDataList<ShopLevelData>(Const.ShopLevelDataSheet);
        var shopLevelToCustomerList = shopLevelList.Select(shopLevel => (level:shopLevel.shopLevel, tenMinCustomer:shopLevel.customer));
        
#if UNITY_EDITOR || DEVELOPMENT_BUILD
        CheckAndFixStock();
        StockFlavorLog();
#endif
        
        // 陳列
        displayFlavors = gameData.ShopStock.Select(x => x).ToList();
        cartView.SetStock(displayFlavors);
        
        // 売り順決定
        shuffledOrder = ShuffleOrder(displayFlavors.Count);
        
        // お店の状態設定
        shopState.Value = gameData.ShopStock.Count == 0 ? ShopState.Close : ShopState.Open;

        // お客さん一覧
        var customerList = new ReactiveCollection<CustomerController>();
        customerList.AddTo(this);
        
        // 購入リクエスト
        var maxOrder = orderPosisionObject.transform.childCount;
        orderSubject
            .Merge(waitCustomerList.ObserveAdd().AsUnitObservable())
            .Merge(shopState.Where(x => x == ShopState.Open).AsUnitObservable())
            .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);
                Debug.Log($"aa order:{count} Request {Time.time} {customer.GetHashCode()}");
                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);
        
        // 購入客
        customerList.ObserveAdd()
            .Select(x => x.Value)
            .Subscribe(customerController =>
            {
                if (shopState.Value == ShopState.Close)
                {
                    customerController.ChangeCustomerState(CustomerState.Leave);
                    return;
                }
                customerController.State
                    .Where(x => x == CustomerState.EatingLeave)
                    .Subscribe(c =>
                    {
                        orderSubject.OnNext(default);
                    }).AddTo(customerController);
                
                // 店に向かう
                customerController.ChangeCustomerState(CustomerState.Wait);
                
                shopState
                    .Where(x => x == ShopState.Close)
                    .Subscribe(x =>
                    {
                        // 売り切れの場合帰る
                        if (customerController.State.Value == CustomerState.Order)
                        {
                            return;
                        }
                        customerController.ChangeCustomerState(CustomerState.Leave);
                    }).AddTo(customerController);
            }).AddTo(this);
        
        requestSubject.BatchFrame().Subscribe(customers =>
        {
            if (gameData.ShopStock.Count == 0)
            {
                shopState.Value = ShopState.Close;
            }
            
            var orders = new List<int>();
            var dontBuyCustomerList = new List<CustomerController>();
            foreach (var controller in customers)
            {
                // 売り切れ
                if (shuffledOrder.Count == 0)
                {
                    controller.ShowWantFlavor(displayFlavors.RandomChoose());
                    controller.CallWaitForSeconds(1.5f, () =>
                    {
                        controller.ShowComplain();
                        controller.ChangeCustomerState(CustomerState.SadLeave);
                    });
                    dontBuyCustomerList.Add(controller);
                    continue;
                }
                
                // オーダー数
                var orderCount = Random.Range(1,5);
                // 在庫取得
                var tmpOrderCount = Mathf.Min(orderCount, shuffledOrder.Count);
                // shuffledOrder順に販売
                orders.AddRange(shuffledOrder.GetRange(0, tmpOrderCount));
                shuffledOrder.RemoveRange(0, tmpOrderCount);

                // コーンの味吹き出しを設定(レア度優先など
                controller.ShowWantFlavor(displayFlavors[orders.RandomChoose()]);
            }
            foreach (var customerController in dontBuyCustomerList)
            {
                customers.Remove(customerController);
            }
            if (orders.Count == 0)
            {
                return;
            }
            
            // 購入
            var flavors = orders.Select(x => (displayFlavors[x], ProductRarity.Normal)).ToList();
#if UNITY_EDITOR
            Debug.Log($"bb order: {orders.Count} {orders.Aggregate("", (s, i) => $"{s},{i}")}");
            Debug.Log($"bb shuffledOrder:{shuffledOrder.Count} {shuffledOrder.Aggregate("", (s, i) => $"{s},{i}")}");
#endif
            var coin = SellPopcorn(flavors);
            var remainStockCount = gameData.ShopStock.Count;
                
            // 自動補充 refill
            RefillProduct();
            StockFlavorLog();

            // 補充された場合フレーバー再設定
            var setStockFlag = false;
            if (gameData.ShopStock.Count == ShopStockCount)
            {
                shuffledOrder.AddRange(orders);
                var stock = gameData.ShopStock.GetRange(remainStockCount, orders.Count).ToArray();
                for (int i = 0; i < orders.Count; i++)
                {
                    displayFlavors[orders[i]] = stock[i];
                }
            }
            else if (gameData.ShopStock.Count <= 13 && shuffledOrder.Exists(x => x > 13))
            {
                displayFlavors = gameData.ShopStock.Select(x => x).ToList();
                shuffledOrder = ShuffleOrder(displayFlavors.Count);
                setStockFlag = true;
            }
            else if (gameData.ShopStock.Count <= 7 && shuffledOrder.Exists(x => x > 7))
            {
                displayFlavors = gameData.ShopStock.Select(x => x).ToList();
                shuffledOrder = ShuffleOrder(displayFlavors.Count);
                setStockFlag = true;
            }
            
            this.CallWaitForSeconds(1.5f, () =>
            {
                foreach (var order in orders)
                {
                    cartView.SellStock(order);
                }
                blueView.SellAction();
                // コイン獲得
                CoinEffect(coin);
                CoinManager.Instance.AddCoinWithEffect(coin, () => { });
                gameData.Coin = CoinManager.Instance.OwnCoin;
                gameData.Heart += customers.Count;
                GameDataManager.SaveGameData();
                shopState.Value = gameData.ShopStock.Count == 0 ? ShopState.Close : ShopState.Open;

                // 表示更新(陳列13=650ms,7=350ms)
                this.CallWaitForSeconds(1f, () =>
                {
                    foreach (var customerController in customers)
                    {
                        customerController.HideOrderPopup();
                        var heartAnimation = customerController.GetComponent<HeartAnimation>();
                        heartAnimation.SetTarget(HeartMeter.Instance.transform);
                        heartAnimation.GetHeart(() =>
                        {
                            HeartMeter.Instance.AddHeart(1);
                        });
                        customerController.ChangeCustomerState(CustomerState.EatingLeave);
                    }
                    stockView.SetStock(gameData.StorageTanks);
                    if (gameData.ShopStock.Count == ShopStockCount)
                    {
                        // 補充したフレーバーのスキンを設定
                        foreach (var order in orders)
                        {
                            cartView.Refill(order, displayFlavors[order]);
                        }
                    }
                    else if (setStockFlag)
                    {
#if UNITY_EDITOR
                        Debug.Log($"bb setStockFlag {displayFlavors.Count} {shuffledOrder.Count}");
#endif
                        // 陳列
                        cartView.ResetDisplay();
                        for (int i = 0; i < displayFlavors.Count; i++)
                        {
                            var index = i;
                            this.CallWaitForSeconds(.05f * i, () =>
                            {
                                if (!shuffledOrder.Contains(index))
                                {
#if UNITY_EDITOR
                                    Debug.Log($"bb no refill");
#endif
                                    return;
                                }
                                cartView.Refill(index, displayFlavors[index]);
                            });
                        }
                    }
                });
            });
        }).AddTo(this);

        // お客さん出現タイマー（店舗レベル連動
        var changeCustomerFlowObservable = HeartMeter.Instance.ShopLevel
            .Select(x => (float)60 * 10 / shopLevelToCustomerList.First(y => y.level == x).tenMinCustomer); // 10分間期待値から来客の間隔を算出
        var customerObservable = changeCustomerFlowObservable
            .Select(x => Observable.Interval(TimeSpan.FromSeconds(x))
                .Select(_ =>
                {
                    Debug.Log($"customer:{GetHashCode()}");
                    // 一般客orセレブ
                    // 複数パターンある場合ChooseRandom
                    return (isCustomer: true, isSpecial: false);
                }))
            .Switch();
        
        // 歩行者出現頻度、立ち止まり確率も設定(歩行者タイマー1分間に6人
        var walkerObservable = Observable.Timer(TimeSpan.FromSeconds(2f), TimeSpan.FromSeconds(10f))
            .Select(x =>
            {
                Debug.Log($"walker:{GetHashCode()}");
                // 一般客orセレブ
                // 複数パターンある場合ChooseRandom
                return (isCustomer: false, isSpecial: false);
            });
        
        // 宣伝時、タップすると60秒だけ稼働するストリーム
        // タップ, リアクティブコマンドで60秒押せない,その後復活
        var adClickObservable = new Subject<Unit>();
        adClickObservable.AddTo(this);
        var adWalkerObservable = Observable.Interval(TimeSpan.FromSeconds(3f))
            .Take(20)
            .Select(x =>
            {
                Debug.Log($"adWalker");
                return (isCustomer: false, isSpecial: false);
            });
        var adClickWalkerObservable = adClickObservable
            .Do(_ => { Debug.Log($"clicked");})
            .Delay(TimeSpan.FromSeconds(2f))    
            .Select(x => adWalkerObservable).Switch();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
        if (UsayaStorageManager.LoadOrDefault(UsayaStorageFilename.Settings_Data, "DebugManyWalker", false))
        {
            Observable.Timer(TimeSpan.FromSeconds(1f), TimeSpan.FromSeconds(70f)).Subscribe(_ => { adClickObservable.OnNext(default); }).AddTo(this);
        }
#endif

        // キャラ生成
        Observable.Merge(walkerObservable, customerObservable)
        .Merge(adClickWalkerObservable)
        .Subscribe(x =>
        {
            var customer = Instantiate(customerPrefab);
            var customerController = customer.GetComponent<CustomerController>();
            customerController.Setup(orderPosisionObject.transform.GetComponentsInChildren<Transform>().ToList().Skip(1).ToList());
            customerController.MoveEndObservable
                .SkipLatestValueOnSubscribe()
                .DistinctUntilChanged()
                .Subscribe(type =>
                {
                    // Debug.Log($"move end {type}");
                    switch (type)
                    {
                        case CustomerMovingType.WalkSide:
                        case CustomerMovingType.WalkSideEat:
                            customerList.Remove(customerController);
                            Destroy(customer);
                            break;
                        case CustomerMovingType.WalkCenter:
                            if (shopState.Value == ShopState.Close)
                            {
                                customerController.ChangeCustomerState(CustomerState.Leave);
                            }
                            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);

            var customerObject = customer.transform.GetChild(0).gameObject;
            var eventTrigger = customerObject.AddComponent<ObservableEventTrigger>();
            if (x.isCustomer)
            {
                // 近くまで歩く(タップされたらcustomerList.Add()
                customerController.ChangeCustomerState(CustomerState.WalkShop);
                eventTrigger.OnPointerClickAsObservable()
                    .TakeUntil(customerController.MoveEndObservable.Where(type => type == CustomerMovingType.WalkCenter))
                    .TakeWhile(_ => shopState.Value != ShopState.Close)
                    .Take(1)
                    .Subscribe(_ =>
                    {
                        customerController.ShowTapReaction();
                        customerList.Add(customerController);
                        if (customerObject.TryGetComponent(typeof(Collider2D), out var target))
                        {
                            Destroy(target);
                        }
                    }).AddTo(customerController);
            }
            else
            {
                // 歩行者はタップ後customerList.Add()
                customerController.ChangeCustomerState(CustomerState.Walk);
                eventTrigger.OnPointerClickAsObservable()
                    .TakeWhile(_ => shopState.Value != ShopState.Close)
                    .Take(1)
                    .Subscribe(_ =>
                    {
                        customerController.ShowTapReaction();
                        customerList.Add(customerController);
                        if (customerObject.TryGetComponent(typeof(Collider2D), out var target))
                        {
                            Destroy(target);
                        }
                    }).AddTo(customerController);
            }
        }).AddTo(this);

        CompositeDisposable shopStateCompositeDisposable = new CompositeDisposable();
        shopStateCompositeDisposable.AddTo(this);
        shopState.Subscribe(state =>
        {
            shopStateCompositeDisposable.Clear();
            switch (state)
            {
                case ShopState.Open:
                    pinkView.SetNormal();
                    closeSign.SetActive(false);
                    break;
                case ShopState.Busy:
                    break;
                case ShopState.Close:
                    if (customerList.Count(x => x.State.Value == CustomerState.Order) == 0)
                    {
                        pinkView.SetSleepy();
                        closeSign.SetActive(true);
                    }
                    else
                    {
                        // お客さんがいなくなったあと、閉店の看板を出す
                        Observable.CombineLatest(customerList.Select(x => x.State).Where(x => x.Value == CustomerState.Order))
                            .First(states => states.Count(x => x == CustomerState.Order) == 0)
                            .Delay(TimeSpan.FromSeconds(2f))
                            .Subscribe(_ =>
                            {
                                pinkView.SetSleepy();
                                closeSign.SetActive(true);
                            }).AddTo(shopStateCompositeDisposable);
                    }

                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(state), state, null);
            }
        }).AddTo(this);

        // お客さんが少なくなったら弟が走る
        customerList.ObserveCountChanged(true).AsUnitObservable()
            .Merge(Observable.Interval(TimeSpan.FromSeconds(1f)).AsUnitObservable())
            .Subscribe(_ =>
            {
                var count = customerList.Count(c => c.State.Value == CustomerState.Wait || c.State.Value == CustomerState.Order);
                if (count < pinkView.FewerBorder)
                {
                    pinkView.SetWalk();
                }
            }).AddTo(this);
    }

    private int SellPopcorn(List<(int flavor, ProductRarity rarity)> flavors)
    {
        var gameData = GameDataManager.GameData;
        // 品切れ
        if (gameData.ShopStock.Count == 0)
        {
            return 0;
        }

        // フレーバーを売る
        var recipeList = RecipeData.GetAllRecipe();
        var rarityList = SpreadsheetDataManager.Instance.GetBaseDataList<RarityData>(Const.RarityDataSheet);
        var coin = 0;
        foreach (var flavorData in flavors)
        {
            var targetIndex = gameData.ShopStock.FindIndex(x => x == flavorData.flavor);
            var flavorRecipe = recipeList.First(data => data.RecipeId == gameData.ShopStock[targetIndex]);
            var rarityData = rarityList.First(data => data.Rarity == flavorData.rarity);
            gameData.ShopStock.RemoveAt(targetIndex);
            gameData.AddSalesCount(flavorData.flavor, 1, flavorData.rarity);
            coin += flavorRecipe.Price * (1 + rarityData.rate / 100);
        }
        return coin;
    }

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

    private void RefillProduct()
    {
        // 手前のタンクから出し多分stockをへらす
        // へらした分を店頭リストに追加する
        var gameData = GameDataManager.GameData;
        var shopSpace = ShopStockCount - gameData.ShopStock.Count;
        var index = 0;
        var tankCount = gameData.StorageTanks.Count;
        while (shopSpace > 0)
        {
            if (index > tankCount - 1)
            {
                break;
            }
            var tank = gameData.StorageTanks[index];
            var stockCount = Mathf.Min(shopSpace, tank.Stock);
            gameData.ShopStock.AddRange(Enumerable.Repeat(tank.FlavorId, stockCount));
            shopSpace -= stockCount;
            tank.Stock -= stockCount;
            gameData.StorageTanks[index] = tank;
            index++;
        }
        GameDataManager.SaveGameData();
    }

    private void CoinEffect(int count)
    {
        var effect = Instantiate(coinPrefab, Vector3.zero, Quaternion.identity, rootTransform);
        effect.GetComponentInChildren<TextMeshProUGUI>().text = count.ToString();
        Destroy(effect.gameObject, 1f);
    }
    
    private void CheckAndFixStock()
    {
        if (GameDataManager.GameData.ShopStock.Count > ShopStockCount)
        {
            GameDataManager.GameData.ShopStock.RemoveRange(0, GameDataManager.GameData.ShopStock.Count - ShopStockCount);
        }
    }

    public static void StockFlavorLog()
    {
        var shopStockString = "";
        foreach (var data in RecipeData.GetAllRecipe())
        {
            var shopStockCount = GameDataManager.GameData.ShopStock.FindAll(x => x == 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);
    }
}
