using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using common;
using eagle;
using Google.Protobuf;
using Net.Eagle0.Common;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Shardok.Common;
using Shardok;
using SimpleFileBrowser;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using GameId = System.Int64;
using Hostility = Net.Eagle0.Common.Hostility;
using Random = System.Random;
using VictoryCondition = Net.Eagle0.Common.VictoryCondition;

public class CustomBattleHandler : MonoBehaviour, IClientConnectionSubscriber {
    public RollPanelController rollPanelController;

    public GameObject yourArmiesArea;
    public GameObject aiArmiesArea;
    public Canvas shardokCanvas;

    public Toggle defenderToggle;
    public TMP_Dropdown mapDropdown;
    public TMP_Dropdown monthDropdown;
    public EventBasedTable yourUnitsController;
    public EventBasedTable aiUnitsController;

    public Toggle customMapToggle;
    public Button chooseMapButton;

    private EagleConnection _eagleConnection;
    private PersistentClientConnection _persistentClientConnection;

    private List<string> _mapNames;
    private readonly List<int> _defenderStartingPositions = new List<int>();
    private List<int> _attackerStartingPositions = null;

    private NewGameRequest _gameRequest;

    private ShardokGameModel _shardokModel;

    public void SetActive(
            EagleConnection conn,
            PersistentClientConnection persistentClientConnection) {
        _eagleConnection = conn;
        _persistentClientConnection = persistentClientConnection;

        var random = new Random();
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string gameId = new string(
                Enumerable.Repeat(chars, 25).Select(s => s[random.Next(s.Length)]).ToArray());

        _gameRequest = new NewGameRequest { GameId = gameId, Month = monthDropdown.value + 1 };

        _mapNames = HexMapStorage.Instance.MapNames();
        _mapNames.Sort();

        mapDropdown.options =
                _mapNames.Select(mapName => new TMP_Dropdown.OptionData(mapName)).ToList();
        MapSelected(0);

        AddYourArmy();
        yourArmiesArea.SetActive(true);

        AddAIArmy();
        aiArmiesArea.SetActive(true);
    }

    private bool _youAreDefender;
    readonly Dictionary<int, string> _heroNames = new();
    readonly Dictionary<int, string> _heroImages = new();
    public void HandleCustomBattleResponse(CustomBattleResponse newGameResponse) {
        Int16 yourPid = (Int16)(_youAreDefender ? 1 : 0);

        HexMap map = _gameRequest.CustomMapBytes.IsEmpty
                             ? HexMapStorage.Instance.WithName(_gameRequest.HexMapName)
                             : HexMap.Parser.ParseFrom(_gameRequest.CustomMapBytes);

        var players = new List<ShardokGameModel.PlayerWithHostility> {
            new() { playerId = yourPid, factionName = "You", hostility = Hostility.SelfHostility },
            new() {
                playerId = _youAreDefender ? 0 : 1,
                factionName = "AI",
                hostility = Hostility.EnemyHostility
            }
        };

        _shardokModel = new ShardokGameModel(
                eagleGameId: newGameResponse.EagleGameId,
                shardokGameId: newGameResponse.ShardokGameId,
                playerId: yourPid,
                persistentClientConnection: _persistentClientConnection,
                map: map,
                locationName: "Custom Map",
                month: _gameRequest.Month,
                players: players,
                heroNames: _heroNames,
                heroImages: _heroImages,
                battalionNames: new Dictionary<int, string> { { 0, "no name" } },
                rollFetcher: rollPanelController);

        shardokCanvas.gameObject.SetActive(true);
        shardokCanvas.GetComponent<ShardokGameController>().SetUpGame(_shardokModel);

        GameId = newGameResponse.EagleGameId;
        Register();
        this.gameObject.SetActive(false);
    }
    public void StartCustomBattle() {
        int nextEagleHeroId = 1;

        _youAreDefender = defenderToggle.isOn;

        var yourSetupInfo = new PlayerSetupInfo {
            UserName = _eagleConnection.playerName,
            Defender = _youAreDefender,
            EagleFactionId = 1,
            Food = 2000,
            Type = PlayerSetupInfo.Types.PlayerType.Human
        };

        yourSetupInfo.VictoryConditions.Add(VictoryCondition.LastPlayerStanding);
        yourSetupInfo.VictoryConditions.Add(
                _youAreDefender ? VictoryCondition.WinAfterMaxRounds
                                : VictoryCondition.HoldsCriticalTiles);

        for (int i = 0; i < yourUnitsController.RowCount; i++) {
            var unit = yourUnitsController.ComponentAt<CustomUnitRowController>(i).Unit;
            unit.Hero.EagleHeroId = nextEagleHeroId;
            yourSetupInfo.Units.Add(unit);
            _heroNames[nextEagleHeroId] = unit.Hero.NameTextId;
            _heroImages[nextEagleHeroId] = "no_profession/male/000107.1333771077.png";
            nextEagleHeroId++;
        }

        var aiSetupInfo = new PlayerSetupInfo {
            UserName = "AI",
            Defender = !_youAreDefender,
            EagleFactionId = 2,
            Food = 2000,
            Type = PlayerSetupInfo.Types.PlayerType.Ai,
        };

        aiSetupInfo.VictoryConditions.Add(VictoryCondition.LastPlayerStanding);
        aiSetupInfo.VictoryConditions.Add(
                _youAreDefender ? VictoryCondition.HoldsCriticalTiles
                                : VictoryCondition.WinAfterMaxRounds);

        for (int i = 0; i < aiUnitsController.RowCount; i++) {
            var unit = aiUnitsController.ComponentAt<CustomUnitRowController>(i).Unit;
            unit.Hero.EagleHeroId = nextEagleHeroId;
            aiSetupInfo.Units.Add(unit);
            _heroNames[nextEagleHeroId] = unit.Hero.NameTextId;
            nextEagleHeroId++;
        }

        _gameRequest.Month = monthDropdown.value + 1;

        _gameRequest.UserInfos.Clear();
        List<PlayerSetupInfo> userInfos =
                _youAreDefender ? new List<PlayerSetupInfo>() { aiSetupInfo, yourSetupInfo }
                                : new List<PlayerSetupInfo>() { yourSetupInfo, aiSetupInfo };
        _gameRequest.UserInfos.Add(userInfos);

        _persistentClientConnection.SendUpdateStreamRequestAsync(new UpdateStreamRequest {
            CustomBattleRequest = new CustomBattleRequest { NewGameRequest = _gameRequest }
        });
    }

    private void Register() { _persistentClientConnection.Subscribe(this); }

    public GameId? CurrentShardokToken(string shardokGameId) { return _shardokModel.History.Count; }

    public void ReceiveGameUpdate(GameUpdate update) {
        if (update == null) {
            Register();
        } else {
            var gameUpdate = update;
            switch (gameUpdate.GameUpdateDetailsCase) {
                case GameUpdate.GameUpdateDetailsOneofCase.ShardokActionResultResponse:
                    foreach (var resp in gameUpdate.ShardokActionResultResponse
                                     .ShardokGameResponses) {
                        _shardokModel.HandleUpdates(
                                resp.ActionResultViews,
                                resp.NewResultViewCount);
                        _shardokModel.HandleAvailableCommands(resp.AvailableCommands);
                    }

                    break;

                default:
                    throw new ArgumentException("Shouldn't be getting other kinds of response");
            }
        }
    }

    private void SetUpMap(HexMap hexMap, string mapName, ByteString mapBytes) {
        _attackerStartingPositions = new List<int>();
        for (int i = 0; i < hexMap.AttackerStartingPositions.Count; i++) {
            if (hexMap.AttackerStartingPositions[i] != null &&
                hexMap.AttackerStartingPositions[i].Positions.Count > 0) {
                _attackerStartingPositions.Add(i);
            }
        }

        _gameRequest.HexMapName = mapName;
        _gameRequest.CustomMapBytes = mapBytes;
        UpdateStartingPositions();
    }

    public void MapSelected(int index) {
        string mapName = _mapNames[index];

        var hexMap = HexMapStorage.Instance.WithName(mapName);

        SetUpMap(hexMap, mapName, ByteString.Empty);
    }

    public void DefenderToggleClicked() { UpdateStartingPositions(); }

    private void UpdateStartingPositions() {
        var yourStartingPositions =
                defenderToggle.isOn ? _defenderStartingPositions : _attackerStartingPositions;
        for (int i = 0; i < yourUnitsController.RowCount; i++) {
            yourUnitsController.ComponentAt<CustomUnitRowController>(i).AvailableStartingPositions =
                    yourStartingPositions;
            yourUnitsController.ComponentAt<CustomUnitRowController>(i).startingPosition.value =
                    i / 10;
        }

        var aiStartingPositions =
                defenderToggle.isOn ? _attackerStartingPositions : _defenderStartingPositions;
        for (int i = 0; i < aiUnitsController.RowCount; i++) {
            aiUnitsController.ComponentAt<CustomUnitRowController>(i).AvailableStartingPositions =
                    aiStartingPositions;
            aiUnitsController.ComponentAt<CustomUnitRowController>(i).startingPosition.value =
                    i / 10;
        }
    }

    private static int NextStartingPositionIndex(
            int startingPositionCount,
            EventBasedTable unitsTable) {
        int[] currentCounts = new int[startingPositionCount];
        for (int i = 0; i < unitsTable.RowCount; i++) {
            currentCounts[unitsTable.ComponentAt<CustomUnitRowController>(i)
                                  .startingPosition.value]++;
        }

        for (int i = 0; i < startingPositionCount; i++) {
            if (currentCounts[i] < 10) { return i; }
        }
        // we couldn't find an available starting position
        throw new ArgumentException("too many units");
    }

    public void AddYourArmy() {
        List<int> startingPositions;
        int nextStartingPos;
        if (defenderToggle.isOn) {
            startingPositions = _defenderStartingPositions;
            nextStartingPos = 0;
        } else {
            startingPositions = _attackerStartingPositions;
            nextStartingPos =
                    NextStartingPositionIndex(startingPositions.Count, yourUnitsController);
        }

        var row = yourUnitsController.AddRowWithComponent<CustomUnitRowController>();
        row.AvailableStartingPositions = startingPositions;
        row.startingPosition.value = nextStartingPos;
    }

    public void AddAIArmy() {
        List<int> startingPositions;
        int nextStartingPos;
        if (defenderToggle.isOn) {
            startingPositions = _attackerStartingPositions;
            nextStartingPos = NextStartingPositionIndex(startingPositions.Count, aiUnitsController);
        } else {
            startingPositions = _defenderStartingPositions;
            nextStartingPos = 0;
        }

        var row = aiUnitsController.AddRowWithComponent<CustomUnitRowController>();
        row.AvailableStartingPositions = startingPositions;
        row.startingPosition.value = nextStartingPos;
    }

    public void LoadGameRequest() { StartCoroutine(ShowLoadGameRequestCoroutine()); }

    private void LoadGameRequest(NewGameRequest request) {
        var gameId = request.GameId + _gameRequest.GameId;
        _gameRequest = request;
        _gameRequest.GameId = gameId;

        monthDropdown.value = _gameRequest.Month - 1;
        mapDropdown.value = mapDropdown.options.Select(opt => opt.text)
                                    .ToList()
                                    .IndexOf(_gameRequest.HexMapName);
        MapSelected(mapDropdown.value);

        // FIXME: two hardcoded players
        var yourInfo = _gameRequest.UserInfos.Last();
        var aiInfo = _gameRequest.UserInfos.First();

        if (!yourInfo.Defender) { throw new ArgumentException("Second player must be defender"); }
        defenderToggle.isOn = true;

        // FIXME: put some of this stuff straight into the gameinfo
        yourUnitsController.RowCount = yourInfo.Units.Count();
        for (int i = 0; i < yourInfo.Units.Count(); i++) {
            yourUnitsController.ComponentAt<CustomUnitRowController>(i).AvailableStartingPositions =
                    defenderToggle.isOn ? _defenderStartingPositions : _attackerStartingPositions;
            yourUnitsController.ComponentAt<CustomUnitRowController>(i).Unit = yourInfo.Units[i];
        }

        aiUnitsController.RowCount = aiInfo.Units.Count();
        for (int i = 0; i < aiInfo.Units.Count(); i++) {
            aiUnitsController.ComponentAt<CustomUnitRowController>(i).AvailableStartingPositions =
                    defenderToggle.isOn ? _attackerStartingPositions : _defenderStartingPositions;
            aiUnitsController.ComponentAt<CustomUnitRowController>(i).Unit = aiInfo.Units[i];
        }
    }

    public void PerfButtonClicked() {
        var index = _mapNames.IndexOf("Alah");
        mapDropdown.value = index;
        MapSelected(index);

        yourUnitsController.RowCount = 0;
        aiUnitsController.RowCount = 0;

        for (int i = 0; i < 6; i++) {
            var rowIndex = i;

            var aiRow = aiUnitsController.AddRowWithComponent<CustomUnitRowController>();
            aiRow.AvailableStartingPositions =
                    defenderToggle.isOn ? _attackerStartingPositions : _defenderStartingPositions;
            aiRow.professionDropdown.value = rowIndex + 1;
            aiRow.battalionTypeDropdown.value = 4;

            var yourRow = yourUnitsController.AddRowWithComponent<CustomUnitRowController>();
            yourRow.AvailableStartingPositions =
                    defenderToggle.isOn ? _defenderStartingPositions : _attackerStartingPositions;
        }
    }

    public void RiversButtonClicked() {
        var index = _mapNames.IndexOf("Chipingia");
        mapDropdown.value = index;
        MapSelected(index);

        aiUnitsController.RowCount = 0;

        var firstRow = aiUnitsController.AddRowWithComponent<CustomUnitRowController>();
        firstRow.SelectedProfession = CommonProfession.Engineer;
        firstRow.SelectedBattalionTypeId = CommonBattalionTypeId.HeavyInfantry;
        firstRow.AvailableStartingPositions =
                defenderToggle.isOn ? _attackerStartingPositions : _defenderStartingPositions;

        for (int i = 1; i < 5; i++) {
            var row = aiUnitsController.AddRowWithComponent<CustomUnitRowController>();
            row.SelectedBattalionTypeId = CommonBattalionTypeId.HeavyCavalry;
            row.AvailableStartingPositions =
                    defenderToggle.isOn ? _attackerStartingPositions : _defenderStartingPositions;
        }
    }

    public void CustomMapToggleClicked() {
        chooseMapButton.gameObject.SetActive(customMapToggle.isOn);
        mapDropdown.gameObject.SetActive(!customMapToggle.isOn);

        if (customMapToggle.isOn) {
            if (_gameRequest.CustomMapBytes.IsEmpty) { ChooseCustomMapButtonClicked(); }
        } else {
            _gameRequest.CustomMapBytes = ByteString.Empty;
        }
    }

    private void ChooseCustomMapButtonClicked() { StartCoroutine(ShowLoadCustomMapCoroutine()); }

    IEnumerator ShowLoadGameRequestCoroutine() {
        var initialPath = PlayerPrefs.GetString("gameRequestLocation", null);

        FileBrowser.SetFilters(true, new FileBrowser.Filter("Game Requests", ".e0gr"));
        FileBrowser.SetDefaultFilter(".e0gr");
        yield return FileBrowser.WaitForLoadDialog(FileBrowser.PickMode.Files, false, initialPath);

        if (FileBrowser.Success) {
            var path = FileBrowser.Result[0];
            var dirPath = Path.GetDirectoryName(path);
            PlayerPrefs.SetString("gameRequestLocation", dirPath);

            var gameRequestBytes = File.ReadAllBytes(path);
            LoadGameRequest(NewGameRequest.Parser.ParseFrom(gameRequestBytes));
        }
    }

    IEnumerator ShowLoadCustomMapCoroutine() {
        var initialPath = PlayerPrefs.GetString("mapLocation", null);

        FileBrowser.SetFilters(true, new FileBrowser.Filter("Maps", ".e0mj"));
        FileBrowser.SetDefaultFilter(".e0mj");
        yield return FileBrowser.WaitForLoadDialog(FileBrowser.PickMode.Files, false, initialPath);

        if (FileBrowser.Success) {
            var path = FileBrowser.Result[0];

            var mapJson = File.ReadAllText(path);

            var jp = new JsonParser(JsonParser.Settings.Default);
            var map = jp.Parse<HexMap>(mapJson);

            ByteString bs;
            using (var stream = new MemoryStream()) {
                map.WriteTo(stream);
                bs = ByteString.CopyFrom(stream.ToArray());
            }

            SetUpMap(map, "Custom", bs);
        } else {
            customMapToggle.isOn = false;
            CustomMapToggleClicked();
        }
    }

    public GameId GameId { get; private set; }
    public GameId? CurrentEagleToken => null;

    public int LastUnfilteredResultCount => 0;
    public List<IClientConnectionSubscriber.ShardokViewStatus> ShardokViewStatuses =>
            new() { new() {
                shardokGameId = _shardokModel.ShardokGameId,
                filteredResultCount = _shardokModel.History.Count
            } };

    public List<IClientConnectionSubscriber.StreamingTextStatus> StreamingTextStatuses => new();
}
