using System;
using System.IO;
using Google.Protobuf;
using Net.Eagle0.Eagle.Views;
using UnityEngine;

namespace eagle {
    public static class EagleGameStateDiskCache {
        private const int CurrentSchemaVersion = 3;

        [Serializable]
        private class CacheFile {
            public int schemaVersion;
            public long gameId;
            public bool hasPlayerId;
            public int playerId;
            public int unfilteredResultCount;
            public string gameStateViewBase64;
            public string savedAtUtc;
        }

        public class CachedGameState {
            public GameStateView GameStateView { get; set; }
            public int UnfilteredResultCount { get; set; }
        }

        private static string _cacheDirectory;
        public static string LastLoadDiagnostic { get; private set; }

        public static CachedGameState Load(long gameId, int? playerId) {
            EnsureCacheDirectoryInitialized();

            var path = CachePath(gameId, playerId);
            LastLoadDiagnostic = $"path={path}";
            if (!File.Exists(path)) {
                LastLoadDiagnostic = $"file not found at {path}";
                return null;
            }

            try {
                var cache = JsonUtility.FromJson<CacheFile>(File.ReadAllText(path));
                if (cache == null) {
                    LastLoadDiagnostic = $"cache JSON parsed to null at {path}";
                    return null;
                }
                if (cache.schemaVersion != CurrentSchemaVersion) {
                    LastLoadDiagnostic =
                            $"schema mismatch at {path}: cache={cache.schemaVersion} current={CurrentSchemaVersion}";
                    return null;
                }
                if (cache.gameId != gameId) {
                    LastLoadDiagnostic =
                            $"game id mismatch at {path}: cache={cache.gameId} requested={gameId}";
                    return null;
                }
                if (cache.hasPlayerId != playerId.HasValue) {
                    LastLoadDiagnostic =
                            $"player presence mismatch at {path}: cacheHas={cache.hasPlayerId} requestedHas={playerId.HasValue}";
                    return null;
                }
                if (cache.hasPlayerId && cache.playerId != playerId.Value) {
                    LastLoadDiagnostic =
                            $"player id mismatch at {path}: cache={cache.playerId} requested={playerId.Value}";
                    return null;
                }
                if (cache.unfilteredResultCount <= 0) {
                    LastLoadDiagnostic =
                            $"invalid unfiltered count at {path}: {cache.unfilteredResultCount}";
                    return null;
                }
                if (String.IsNullOrEmpty(cache.gameStateViewBase64)) {
                    LastLoadDiagnostic = $"missing GameStateView payload at {path}";
                    return null;
                }

                var bytes = Convert.FromBase64String(cache.gameStateViewBase64);
                var gameStateView = GameStateView.Parser.ParseFrom(bytes);
                if (gameStateView.GameId == 0) {
                    gameStateView.GameId = gameId;
                } else if (gameStateView.GameId != gameId) {
                    LastLoadDiagnostic =
                            $"GameStateView game id mismatch at {path}: view={gameStateView.GameId} requested={gameId}";
                    return null;
                }

                LastLoadDiagnostic = $"loaded {path} count={cache.unfilteredResultCount}";

                return new CachedGameState {
                    GameStateView = gameStateView,
                    UnfilteredResultCount = cache.unfilteredResultCount
                };
            } catch (Exception e) {
                LastLoadDiagnostic = $"exception loading {path}: {e}";
                Debug.LogWarning($"Failed to load Eagle game state cache for game {gameId}: {e}");
                TryDelete(path);
                return null;
            }
        }

        public static void
        Save(long gameId, int? playerId, int unfilteredResultCount, GameStateView gameStateView) {
            if (unfilteredResultCount <= 0 || gameStateView == null) { return; }

            EnsureCacheDirectoryInitialized();

            try {
                Directory.CreateDirectory(CacheDirectory);
                var cache = new CacheFile {
                    schemaVersion = CurrentSchemaVersion,
                    gameId = gameId,
                    hasPlayerId = playerId.HasValue,
                    playerId = playerId ?? 0,
                    unfilteredResultCount = unfilteredResultCount,
                    gameStateViewBase64 = Convert.ToBase64String(gameStateView.ToByteArray()),
                    savedAtUtc = DateTime.UtcNow.ToString("O")
                };

                File.WriteAllText(CachePath(gameId, playerId), JsonUtility.ToJson(cache));
            } catch (Exception e) {
                Debug.LogWarning($"Failed to save Eagle game state cache for game {gameId}: {e}");
            }
        }

        private static void EnsureCacheDirectoryInitialized() {
            if (_cacheDirectory != null) { return; }

            _cacheDirectory =
                    Path.Combine(Application.persistentDataPath, "eagle0", "eagle_game_state");
        }

        private static string CacheDirectory => _cacheDirectory;

        private static string CachePath(long gameId, int? playerId) {
            var playerKey = playerId.HasValue ? playerId.Value.ToString() : "spectator";
            return Path.Combine(CacheDirectory, $"{gameId:X}-{playerKey}.json");
        }

        private static void TryDelete(string path) {
            try {
                File.Delete(path);
            } catch (Exception e) {
                Debug.LogWarning($"Failed to delete corrupt Eagle game state cache {path}: {e}");
            }
        }
    }
}
