using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

// main model class
public sealed class Player
{
    public readonly string Name;
    public NavigationPoint CurrentPoint { get; private set; }
    public readonly StarShip StarShip;
    public readonly NavigationPoint[] NavigationPoints;
    public readonly Planet[] Planets;
    public readonly SpaceStantionType[] StantionTypes;
    public readonly GameParameters GameParams;
    public readonly HyperJump HyperJump = new HyperJump();

    public int Credits { get; private set; }
    public readonly EconomyParameters EconomyParams;
    public readonly Dictionary<string, float> LastCost;

    public int Turn { get; private set; }

    public Player(
        string name,
        int turn,
        StarShip ship,
        NavigationPoint point,
        NavigationPoint[] points,
        Planet[] planets,
        SpaceStantionType[] types,
        GameParameters gameParams,
        EconomyParameters economyParams,
        int credits,
        Dictionary<string, float> lastCost = null)
    {
        Name = name ?? string.Empty;
        NavigationPoints = points;
        Planets = planets;
        CurrentPoint = point;
        StarShip = ship;
        StantionTypes = types;
        Turn = turn;
        EconomyParams = economyParams;
        Credits = credits;
        LastCost = lastCost ?? new Dictionary<string, float>();
        GameParams = gameParams;
        for (int i = 0; i < 10; i++)
        {
            MoveResourcesRandomly();
        }
        AddNewModifiers();
    }

    public bool CheckVictoryCondition()
    {
        return Credits >= GameParams.WinCreditsCount;
    }

    public void MakeEconomyTurn()
    {
        Turn += 1;
        ClearOldModifiers();
        MoveResourcesRandomly();
        AddNewModifiers();
        Debug.Log($"Make turn {Turn}");
    }

    public void StartHyperJump(NavigationPoint target)
    {
        if (target == null)
            return;
        HyperJump.AddJump(CurrentPoint, target, AddRanomEvents(CurrentPoint.SystemPostition, target.SystemPostition));
        StarShip.ConsumeFuel(StarShip.GetFuelCost(Vector3.Distance(CurrentPoint.SystemPostition, target.SystemPostition)));
        CurrentPoint = target;
    }

    private NavigationPoint[] AddRanomEvents(Vector3 start, Vector3 end)
    {
        var distance = Vector3.Distance(start, end);
        var maxCount = Mathf.Clamp(Mathf.RoundToInt(distance * 0.5f), 1, 5);
        var count = Random.Range(1, maxCount) * Random.Range(0, 2);
        if (Turn < 15) // no pirates first 15 flies
            count = 0;
        var points = new NavigationPoint[count];
        Debug.Log($"Add events on distance {distance} max {maxCount} count {count}");
        for (int i = 0; i < points.Length; i++) {
            var position = Vector3.Lerp(start, end, (i + 1) / (float)(points.Length + 1));
            points[i] = new NavigationPoint(-1, -1, position);
            points[i].Ships.Add(CreatePirate());
        }
        return points;
    }

    private StarShip CreatePirate()
    {
        var equip = new ShipEquipment(
            Random.Range(0, StarShip.Equipment.Ship.Id + 1 * Random.Range(0, 2)),
            0, 0, 0, 0, 0, EconomyParams.Blueprints);
        var position = Random.onUnitSphere * Random.Range(15, 35);
        var rotation = Random.rotationUniform;
        var armor = equip.Armor;
        if (Random.Range(0, 100) > 50) { // hitted pirate
            armor = Random.Range(1, equip.Armor);
        }

        const int pirateFactionId = -1;
        var ship = new StarShip(position, rotation, pirateFactionId, equip, armor, 0, GetRandomResources(equip.CargoSpace));
        
        return ship;
    }

    private Dictionary<string, int> GetRandomResources(int space)
    {
        var count = Random.Range(0, space);
        var resources = new Dictionary<string, int>();
        for (int i = 0; i < count; i++) {
            var randomResourceId = EconomyParams.ResourceIds[Random.Range(0, EconomyParams.ResourceIds.Length)];
            if (!resources.ContainsKey(randomResourceId))
                resources[randomResourceId] = 0;
            resources[randomResourceId] += 1;
        }
        return resources;
    }

    public void MoveResourcesRandomly()
    {
        foreach (var point in NavigationPoints)
        {
            foreach (var stantion in point.Stantions)
            {
                stantion.RandomResourceMove();
            }
        }
    }

    private void AddNewModifiers()
    {
        foreach (var point in NavigationPoints)
        {
            foreach (var stantion in point.Stantions)
            {
                if (Random.Range(0, 100) < 50) {
                    AddRandomModifier(stantion);
                }
            }
        }
    }

    private void AddRandomModifier(SpaceStantion stantion)
    {
        var data = EconomyParams.Modifiers[UnityEngine.Random.Range(0, EconomyParams.Modifiers.Length)];
        var modifier = new CostModifier(data.Id, data.ResourceId, data.Percent, Turn + data.TurnsCount);
        stantion.AddModifier(modifier);
        // Debug.Log($"Add modifier {modifier.PublicId} ({modifier.ResourceId}) on {stantion.Name}");
    }

    private void ClearOldModifiers()
    {
        foreach (var point in NavigationPoints)
        {
            foreach (var stantion in point.Stantions)
            {
                stantion.RemoveEndedModifiers(Turn);
            }
        }
    }

    public void AddCredits(int count)
    {
        Credits += count;
    }

    public int GetFuelCost(float distance)
    {
        var fuelCostFactor = 1f;
        return Mathf.CeilToInt(distance * fuelCostFactor * EconomyParams.FuelCost);
    }

    public bool TryBuyRepair()
    {
        const int repairCost = 5;
        var needRepair = StarShip.MaxArmor - StarShip.Armor;
        var fullCost = needRepair * repairCost;
        if (Credits >= fullCost)
        {
            Credits -= fullCost;
            StarShip.Repair();
            return true;
        }
        return false;
    }

    public bool TryBuyHyperFuel(int cost, int count = 1)
    {
        var fullCost = cost * count;
        var haveMoney = Credits >= fullCost;
        if (haveMoney)
        {
            StarShip.ConsumeFuel(-count);
            Credits -= fullCost;

            return true;
        }
        return false;
    }

    public bool TryBuyEquip(int id, EquipmentBlueprintParams blueprint, ShipEquipment.Type type)
    {
        var haveMoney = Credits >= blueprint.BuyCost;
        if (haveMoney)
        {
            Credits -= blueprint.BuyCost;
            Credits += StarShip.Equipment.SetEquip(type, id, blueprint);
            return true;
        }
        return false;
    }

    public bool TryBuy(string resourceId, int cost, int count = 1)
    {
        var fullCost = cost * count;
        var havePlace = StarShip.GetEmptySpace() >= count;
        var haveMoney = Credits >= fullCost;
        if (havePlace && haveMoney)
        {
            var prevCount = StarShip.Resources[resourceId];
            var prevCost = GetLastCost(resourceId);
            StarShip.Resources[resourceId] += count;
            Credits -= fullCost;

            LastCost[resourceId] = (prevCount * prevCost + fullCost) / StarShip.Resources[resourceId];
            return true;
        }
        return false;
    }

    public int GetLastCostRounded(string resourceId) => Mathf.RoundToInt(GetLastCost(resourceId));

    public float GetLastCost(string resourceId)
    {
        if (LastCost.TryGetValue(resourceId, out var cost))
        {
            return cost;
        }
        return 0;
    }

    public bool TrySell(string resourceId, int cost, int count = 1)
    {
        if (StarShip.Resources[resourceId] >= count)
        {
            StarShip.Resources[resourceId] -= count;
            Credits += cost * count;
            if (StarShip.Resources[resourceId] == 0 && LastCost.ContainsKey(resourceId))
                LastCost.Remove(resourceId);
            return true;
        }
        return false;
    }

    public void PiratesStealCargo() // TODO - make partial stealing and move resources to pirate ship (to fight back)
    {
        Debug.Log("Pirates steal cargo");
        foreach (var resourceId in EconomyParams.ResourceIds) {
            if (StarShip.Resources[resourceId] > 0)
            {
                StarShip.Resources[resourceId] -= Random.Range(0, StarShip.Resources[resourceId]);
            }
        }
    }
}
