using System;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public sealed class Game : MonoBehaviour
{
    [SerializeField]
    private int playerFactionId = 0;
    [SerializeField]
    private Camera controlCamera = null;
    [SerializeField]
    private ControlPoint playerPoint = null;
    [SerializeField]
    private ControlPoint enemyPoint = null;
    [SerializeField]
    private ControlPanel controlPanel = null;
    [SerializeField]
    private Text statusText = null;
    [SerializeField]
    private FactionProvider factionProvider = null;
    [SerializeField]
    private UnitFactory unitsFactory = null;
    [SerializeField]
    private SelectionView selectionView = null;
    [SerializeField]
    private BlueprintProvider blueprints = null;
    [SerializeField]
    private GameObject winUI = null;
    [SerializeField]
    private GameObject loseUI = null;
    [SerializeField]
    private EnemyController enemyController = null;
    [SerializeField]
    private int defaultResources = 50;
    [SerializeField]
    private string mainMenuScene = "MainMenu";

    private CameraControlModel cameraModel = null;
    private readonly Plane floor = new Plane(Vector3.up, Vector3.zero);
    private SelectionHelper selection = null;
    private ControlPoint selectedControlPoint = null;
    private StringBuilder props = new StringBuilder();
    private bool isPlaying = false;

    private void Awake()
    {
        cameraModel = new CameraControlModel(OnPointSelect);
        SelectElement(null);
        var playerFaction = new Faction("Player", 0, Color.blue);
        var enemyFaction = new Faction("Overmind", 1, Color.red);
        factionProvider.SetFactions(new Faction[] {
            playerFaction,
            enemyFaction
        });
        BuildDefaultSet(playerFaction, playerPoint, playerPoint.transform.position + new Vector3(1, 0, 1));
        BuildDefaultSet(enemyFaction, enemyPoint, enemyPoint.transform.position - new Vector3(1, 0, 1));

        isPlaying = true;
        winUI.SetActive(false);
        loseUI.SetActive(false);
        enemyController.Init(enemyFaction);
    }

    private void BuildDefaultSet(Faction playerFaction, ControlPoint startPoint, Vector3 armyTarget)
    {
        playerFaction.AddResources(defaultResources);
        unitsFactory.CreateMainBase(playerFaction, startPoint);
        var army = unitsFactory.CreateArmy(playerFaction, startPoint.transform.position);
        army.MoveTo(armyTarget);
    }

    private void OnPointSelect(Vector2 point)
    {
        var element = TrySelectItem(point);
        SelectElement(element);
        if (element == null) {
            SelectControlPoint(TrySelectControlPoint(point));
        }
    }

    private void Update()
    {
        cameraModel.Process();
        CheckWinConditions();
        if (Input.GetMouseButtonDown(1) && selection != null && selection.CanMove() && selection.factionId == playerFactionId)
        {
            TryMoveArmy(Input.mousePosition);
        }

        foreach (var faction in factionProvider.Factions)
            faction.BuildQueue.Build(Time.deltaTime);

        SetStatus(factionProvider.Factions[playerFactionId]);

        controlPanel.UpdateProperties(selection?.GetDescription());

        if (Input.GetKeyDown(KeyCode.Escape))
        {
            SceneManager.LoadScene(mainMenuScene);
        }
    }

    private void CheckWinConditions()
    {
        if (!isPlaying)
            return;
        if (!factionProvider.Factions[playerFactionId].Bases.Any(b => b.IsMainBase))
        {
            loseUI.SetActive(true);
            isPlaying = false;
        }
        if (!factionProvider.Factions[1].Bases.Any(b => b.IsMainBase))
        {
            winUI.SetActive(true);
            isPlaying = false;
        }
    }

    private void SetStatus(Faction faction)
    {
        props.Clear();

        var resourceCount = faction.ResourceUnitCount;
        props.Append($"{resourceCount}\n");

        var currentProject = faction.BuildQueue.CurrentProject;
        if (currentProject != null)
            props.Append($"\n{currentProject.ID} -> ({100 - (int)(faction.BuildQueue.BuildTime * 100)}%)");
        AppendCollapsedQueue(props, faction);

        statusText.text = props.ToString();
    }

    private void AppendCollapsedQueue(StringBuilder props, Faction faction)
    {
        var similar = string.Empty;
        var count = 0;
        foreach (var project in faction.BuildQueue.Queue)
        {
            if (similar != project.ID)
            {
                AppendItem(props, similar, count);
                count = 1;
                similar = project.ID;
            }
            else
            {
                count += 1;
            }
        }
        AppendItem(props, similar, count);
    }

    private void AppendItem(StringBuilder props, string similar, int count)
    {
        if (count == 0)
            return;
        if (count == 1)
        {
            props.Append($"\n{similar} - (0%)");
        }
        else
        {
            props.Append($"\n{similar} ({count}) - (0%)");
        }
    }

    private void TryMoveArmy(Vector3 mousePosition)
    {
        var ray = controlCamera.ScreenPointToRay(mousePosition);
        if (floor.Raycast(ray, out var enterDistance)) {
            var point = ray.GetPoint(enterDistance);
            selection.MoveTo(point);
        }
    }

    private SelectionHelper TrySelectItem(Vector3 point)
    {
        var ray = controlCamera.ScreenPointToRay(point);
        if (Physics.Raycast(ray, out var hitInfo, 50)) {
            return hitInfo.collider.GetComponent<SelectionHelper>();
        }
        return null;
    }

    private ControlPoint TrySelectControlPoint(Vector3 point)
    {
        var ray = controlCamera.ScreenPointToRay(point);
        if (Physics.Raycast(ray, out var hitInfo, 50))
        {
            return hitInfo.collider.GetComponent<ControlPoint>();
        }
        return null;
    }

    private void SelectElement(SelectionHelper selectedItem)
    {
        selection = selectedItem;
        controlPanel.Show(selection?.GetAvailableProjects(), OnProjectSelected);
        SelectControlPoint(null);
        selectionView.SetSelection(selection);

    }

    private void OnProjectSelected(ProjectBlueprint project)
    {
        if (selection != null)
        {
            selection.StartProject(project);
            return;
        }
        if (selectedControlPoint != null) {
            TryBuildOutpost(project, selectedControlPoint);
            return;
        }
        Debug.LogError($"No processor for {project.ID} - all selections is empty");
    }

    private void TryBuildOutpost(ProjectBlueprint blueprint, ControlPoint point)
    {
        var faction = factionProvider.Factions[playerFactionId];
        var mainBase = faction.Bases.FirstOrDefault();
        if (mainBase == null)
        {
            Debug.LogError($"Can't build outpost - main base is empty for {blueprint.ID}");
            return;
        }
        BuildProject project = new BuildProject(blueprint.BuildTime, blueprint.ID, blueprint.ID, (readyPtoject) => {
            unitsFactory.CreateOutpostAssembler(faction, mainBase.transform.position, point);
        });

        faction.BuildProject(project, blueprint.Cost);
    }

    private void SelectControlPoint(ControlPoint controlPoint = null)
    {
        selectedControlPoint = controlPoint;
        if (controlPoint != null && selection == null)
            controlPanel.Show(blueprints.ControlPointProjects, OnProjectSelected);
        if (controlPoint != null && selection == null)
            selectionView.SetObject(controlPoint.transform);
    }
}
