using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using common;
using common.GUIUtils;
using eagle;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Net.Eagle0.Eagle.Api;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = System.Object;

public class AuthInterceptor : Interceptor {
    private readonly string headerName = "Authorization";
    private string headerValue;

    public static string HeaderValue(string name, string password) {
        String toEncode = name + ":" + password;
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(toEncode));
    }

    public AuthInterceptor(String name, String password) {
        headerValue = "Basic " + HeaderValue(name, password);
    }
    public override TResponse BlockingUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            BlockingUnaryCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncUnaryCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncServerStreamingCall<TResponse>
    AsyncServerStreamingCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncClientStreamingCall<TRequest, TResponse>
    AsyncClientStreamingCall<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(ContextWithHeaders(context));
    }
    public override AsyncDuplexStreamingCall<TRequest, TResponse>
    AsyncDuplexStreamingCall<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(ContextWithHeaders(context));
    }

    private ClientInterceptorContext<TRequest, TResponse> ContextWithHeaders<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> original)
            where TRequest : class
            where TResponse : class {
        var headers = new Metadata();
        headers.Add(headerName, headerValue);

        return new ClientInterceptorContext<TRequest, TResponse>(
                original.Method,
                original.Host,
                original.Options.WithHeaders(headers));
    }
};

public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
    public TMP_InputField urlField;
    public TMP_InputField nameField;
    public TMP_InputField passwordField;
    public TMP_Dropdown resolutionDropdown;

    public GameObject connectionPanel;
    public GameObject gameSelectionPanel;
    public GameObject customBattlePanel;
    public ErrorHandler errorHandler;

    public GameObject runningGamesListArea;
    public GameObject runningGamesListItemPrefab;

    public GameObject availableGamesListArea;
    public GameObject availableGamesListItemPrefab;
    public GameObject waitingGamesListItemPrefab;
    public GameObject createGameListItemPrefab;

    public Canvas connectionCanvas;
    public Canvas eagleCanvas;
    public Canvas shardokCanvas;
    public CustomBattleHandler customBattleHandler;

    List<GameInfo> fetchedRunningGames;
    List<AvailableLeader> fetchedNewGameLeaders;
    int fetchedMaxPlayerCount;
    private EagleConnection eagleConnection;
    private PersistentClientConnection _persistentClientConnection;
    private HttpClient _httpClient;
    private bool listen = false;
    private CancellationTokenSource _cancellationTokenSource;
    private readonly Object pendingReplyLock = new Object();

    public ClientPregeneratedText clientPregeneratedText;

    const String DefaultUrl = "eagle0.net";
    const String URLKey = "urlKey";
    const String NameKey = "nameKey";
    const String PasswordKey = "passwordKey";

    public void ReceiveLobbyUpdate(LobbyResponse lobbyResponse) {
        _handleLobbyResponse(lobbyResponse);
    }

    public void ReceivePregeneratedTextUpdate(PregeneratedTextResponse response) {
        clientPregeneratedText.Load(response.PregeneratedText);
    }

    void Start() {
        var currentResolution = Screen.currentResolution;
        var resolutions = Screen.resolutions;

        resolutionDropdown.ClearOptions();
        resolutionDropdown.AddOptions(resolutions.Select(r => $"{r.width} x {r.height}").ToList());
        resolutionDropdown.value = resolutions.ToList().IndexOf(currentResolution);

        urlField.text = PlayerPrefs.GetString(URLKey, DefaultUrl);
        nameField.text = PlayerPrefs.GetString(NameKey, "sample_name");
        passwordField.text = PlayerPrefs.GetString(PasswordKey, "sample_password");

        connectionCanvas.gameObject.SetActive(true);
        eagleCanvas.gameObject.SetActive(false);
        shardokCanvas.gameObject.SetActive(false);

        connectionCanvas.enabled = true;
        connectionPanel.gameObject.SetActive(true);
        gameSelectionPanel.gameObject.SetActive(false);
        customBattlePanel.gameObject.SetActive(false);

        errorHandler.gameObject.SetActive(true);
    }

    public void EditorButtonClicked() { SceneManager.LoadScene("Map Editor"); }

    public void ConnectEagle() { _internalConnectEagle(); }

    public void CustomBattle() { _internalCustomBattle(); }

    public void OnApplicationQuit() { Dispose(); }

    public void Dispose() {
        listen = false;

        // Cancel any ongoing operations using cancellation token
        _cancellationTokenSource?.Cancel();

        _cancellationTokenSource?.Dispose();
        _cancellationTokenSource = null;

        _httpClient?.Dispose();
        _httpClient = null;
        _persistentClientConnection?.Dispose();
        _persistentClientConnection = null;
        eagleConnection?.Dispose();
        eagleConnection = null;
    }

    public void OnResolutionChanged(int dropdownIndex) {
        var newResolution = Screen.resolutions[dropdownIndex];
        Screen.SetResolution(
                newResolution.width,
                newResolution.height,
                FullScreenMode.FullScreenWindow,
                newResolution.refreshRateRatio);
    }

    private void _handleLobbyResponse(LobbyResponse lobbyResponse) {
        fetchedRunningGames = new List<GameInfo>();
        fetchedRunningGames.AddRange(lobbyResponse.RunningGames);

        fetchedNewGameLeaders = new List<AvailableLeader>();
        fetchedNewGameLeaders.AddRange(lobbyResponse.NewGameOptions.AvailableLeaders);

        fetchedMaxPlayerCount = lobbyResponse.NewGameOptions.MaxSupportedPlayers;

        MainQueue.Q.Enqueue(() => {
            ResourceFetcher.headshotFetcher.Prefetch(
                    fetchedNewGameLeaders.Select(a => a.ImagePath));

            connectionPanel.gameObject.SetActive(false);
            gameSelectionPanel.gameObject.SetActive(true);

            // Set up running games table
            foreach (Transform row in runningGamesListArea.transform) { Destroy(row.gameObject); }
            foreach (var runningGame in lobbyResponse.RunningGames) {
                var listItem = Instantiate(
                        runningGamesListItemPrefab,
                        runningGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                listItem.GetComponent<RunningGameItem>().SetRunningGame(
                        runningGame.GameId,
                        runningGame.Leader);
                listItem.GetComponent<RunningGameItem>().GoCallback = this.SelectEagleGame;
            }

            // Set up available/waiting games table
            foreach (Transform row in availableGamesListArea.transform) { Destroy(row.gameObject); }

            foreach (var waitingGame in lobbyResponse.WaitingGames) {
                var listItem = Instantiate(
                        waitingGamesListItemPrefab,
                        availableGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                listItem.GetComponent<WaitingGameItem>().SetWaitingGame(
                        waitingGame.GameId,
                        string.Format(
                                "{0} / {1}",
                                waitingGame.CurrentHumanPlayerCount,
                                waitingGame.MaxHumanPlayerCount),
                        waitingGame.SelectedLeader);
            }

            var createGameItem =
                    Instantiate(createGameListItemPrefab, availableGamesListArea.transform, true);
            // var singlePlayerListItem = Instantiate(availableGamesListItemPrefab);
            createGameItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
            createGameItem.GetComponent<CreateGameItem>().SetCreationOptions(
                    fetchedNewGameLeaders,
                    fetchedMaxPlayerCount);
            createGameItem.GetComponent<CreateGameItem>().CreateCallback = this.CreateGame;

            foreach (var availableGame in lobbyResponse.AvailableGames) {
                var listItem = Instantiate(
                        availableGamesListItemPrefab,
                        availableGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                listItem.GetComponent<AvailableGameItem>().SetAvailableNewGame(
                        availableGame.GameId,
                        string.Format(
                                "{0} / {1}",
                                availableGame.CurrentHumanPlayerCount,
                                availableGame.MaxHumanPlayerCount),
                        availableGame.AvailableLeaders.ToList());
                listItem.GetComponent<AvailableGameItem>().JoinCallback = this.JoinGame;
            }
        });
    }

    private void StartListeningForLobbyUpdates() {
        lock (pendingReplyLock) {
            _persistentClientConnection.SetLobbySubscriber(this);
            _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest { EnterLobbyRequest = new EnterLobbyRequest() });
        }
    }

    private void _createConnection() {
        PlayerPrefs.SetString(URLKey, urlField.text);
        PlayerPrefs.SetString(NameKey, nameField.text);
        PlayerPrefs.SetString(PasswordKey, passwordField.text);

        // Dispose existing connections before creating new ones
        _httpClient?.Dispose();
        _persistentClientConnection?.Dispose();
        eagleConnection?.Dispose();
        _cancellationTokenSource?.Dispose();

        // Initialize cancellation token for thread management
        _cancellationTokenSource = new CancellationTokenSource();

        eagleConnection = new EagleConnection(nameField.text, passwordField.text, urlField.text);
        _persistentClientConnection = new PersistentClientConnection(
                eagleConnection.EagleGrpcClient,
                eagleConnection.credentials,
                EagleConnection.EagleCancellationToken);
        _persistentClientConnection.Connect();

        errorHandler.PersistentClientConnection = _persistentClientConnection;

        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Basic", eagleConnection.authHeader);
        ResourceFetcher.SetUpConnection(_httpClient);
    }

    private void _internalCustomBattle() {
        _createConnection();
        _persistentClientConnection.SetLobbySubscriber(this);

        SetCustomBattleActive(true);

        connectionPanel.SetActive(false);
        customBattlePanel.SetActive(true);
    }

    private void RequestMaps() {
        var mapNames = HexMapStorage.Instance.MapNames();
        var mapInfos = mapNames.Select(mapName => {
                                   var existingMap = HexMapStorage.Instance.WithName(mapName);
                                   return new HexMapsRequest.Types.OneMapRequestInfo {
                                       MapName = mapName,
                                       MapHash = HexMapStorage.Instance.HashForName(mapName)
                                   };
                               })
                               .ToList();

        var hexMapsRequest = new HexMapsRequest();
        hexMapsRequest.MapInfos.AddRange(mapInfos);

        _persistentClientConnection.SendUpdateStreamRequestAsync(
                new UpdateStreamRequest { HexMapsRequest = hexMapsRequest });
    }

    private void _internalConnectEagle() {
        SetLobbyActive(true);
        connectionCanvas.enabled = true;

        _createConnection();

        try {
            _persistentClientConnection.SendUpdateStreamRequestAsync(new UpdateStreamRequest {
                PregeneratedTextRequest =
                        new PregeneratedTextRequest {
                            KnownPregeneratedTextHash = clientPregeneratedText.Hash
                        }
            });
            RequestMaps();
            StartListeningForLobbyUpdates();
        } catch (RpcException e) {
            MainQueue.Q.Enqueue(() => { errorHandler.Add(e); });
        } catch (Exception e) {
            MainQueue.Q.Enqueue(() => { errorHandler.Add(e); });
        }
    }

    private void JoinGame(long gameId, string leaderName) {
        var game = _internalJoinEagleGame(gameId, leaderName);
    }

    private void CreateGame(string leaderNameTextId, int humanPlayerCount, int totalPlayerCount) {
        var game = _internalCreateGame(leaderNameTextId, humanPlayerCount, totalPlayerCount);
    }

    public void QuitButtonClicked() { Application.Quit(); }

    public void HandleJoinGameResponse(JoinGameResponse response) {
        try {
            switch (response.Result) {
                case JoinGameResult.SuccessJoinGameResult: break;

                default:
                    // FIXME: display something
                    break;
            }
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    public void HandleCreateGameResponse(CreateGameResponse response) {
        try {
            switch (response.Result) {
                case JoinGameResult.SuccessJoinGameResult:
                    SelectEagleGame(response.GameId);
                    // Automatically start the game if it's one player?
                    break;

                default:
                    // FIXME: display something
                    break;
            }
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    public void HandleCustomBattleResponse(CustomBattleResponse response) {
        try {
            customBattleHandler.HandleCustomBattleResponse(response);
            customBattleHandler.SetActive(eagleConnection, _persistentClientConnection);
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    private async Task<bool>
    _internalCreateGame(string desiredLeaderTextId, int humanPlayerCount, int totalPlayerCount) {
        try {
            return await _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest {
                        CreateGameRequest =
                                new CreateGameRequest {
                                    DesiredLeaderTextId = desiredLeaderTextId,
                                    TotalPlayerCount = totalPlayerCount,
                                    HumanPlayerCount = humanPlayerCount
                                }
                    });
        } catch (WebException e) {
            Debug.Log(String.Format("Caught exception in BeginGetResponse {0}", e.Message));
            SetLobbyActive(true);
            return false;
        }
    }

    private async Task<bool> _internalJoinEagleGame(long gameId, string leaderNameTextId) {
        try {
            return await _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest {
                        JoinGameRequest =
                                new JoinGameRequest {
                                    GameId = gameId,
                                    DesiredLeaderTextId = leaderNameTextId
                                }
                    });
        } catch (WebException e) {
            Debug.Log(String.Format("Caught exception in BeginGetResponse {0}", e.Message));
            SetLobbyActive(true);
            return false;
        }
    }

    private void SetLobbyActive(bool active) {
        runningGamesListArea.SetActive(active);
        availableGamesListArea.SetActive(active);
    }

    private void SetCustomBattleActive(bool active) {
        customBattleHandler.SetActive(eagleConnection, _persistentClientConnection);
    }

    private void SelectEagleGame(long gameId) {
        var gameInfo = fetchedRunningGames.Find(gi => gi.GameId == gameId);

        if (gameInfo != null) {
            SetLobbyActive(false);
            eagleCanvas.GetComponent<EagleGameController>().SetUpGame(
                    gameInfo.GameId,
                    gameInfo.FactionId,
                    _persistentClientConnection);
            connectionCanvas.enabled = false;
            connectionCanvas.gameObject.SetActive(false);
            eagleCanvas.gameObject.SetActive(true);
        }
    }
}
