using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Npgsql;
using PhoenixLib.Logging;
using WingsAPI.Data.GameData;
using WingsEmu.DTOs.Maps;

namespace Plugin.ResourceLoader.Loaders
{
    public class MapResourceFileLoader : IResourceLoader<MapDataDTO>
    {
        private readonly ResourceLoadingConfiguration _config;


        public MapResourceFileLoader(ResourceLoadingConfiguration config) => _config = config;

        public async Task<IReadOnlyList<MapDataDTO>> LoadAsync()
        {
            string filePath = Path.Combine(_config.GameDataPath, "MapIDData.dat");

            if (!File.Exists(filePath))
            {
                bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
                               || string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
                if (dbFirst)
                {
                    TryHydrateDatFileFromDatabase("MapIDData.dat", filePath);
                    TryHydrateMapFilesFromDatabase();
                }
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException($"{filePath} should be present");
            }

            var maps = new List<MapDataDTO>();
            var dictionaryId = new Dictionary<int, string>();

            int i = 0;
            using (var mapIdStream = new StreamReader(filePath, Encoding.GetEncoding(1252)))
            {
                string line;
                while ((line = await mapIdStream.ReadLineAsync()) != null)
                {
                    string[] values = line.Split(' ');
                    if (values.Length <= 1)
                    {
                        continue;
                    }

                    if (!int.TryParse(values[0], out int mapId))
                    {
                        continue;
                    }

                    if (!dictionaryId.ContainsKey(mapId))
                    {
                        dictionaryId.Add(mapId, values[4]);
                    }
                }
            }

            if (!Directory.Exists(_config.GameMapsPath) || !new DirectoryInfo(_config.GameMapsPath).GetFiles().Any())
            {
                bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
                               || string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
                if (dbFirst)
                {
                    TryHydrateMapFilesFromDatabase();
                }
            }

            foreach (FileInfo file in new DirectoryInfo(_config.GameMapsPath).GetFiles())
            {
                string name = string.Empty;

                if (dictionaryId.TryGetValue(int.Parse(file.Name), out string value))
                {
                    name = value;
                }


                byte[] data = await File.ReadAllBytesAsync(file.FullName);
                short width = BitConverter.ToInt16(data, 0);
                short height = BitConverter.ToInt16(data, 2);

                maps.Add(new MapDataDTO
                {
                    Id = short.Parse(file.Name),
                    Name = name,
                    Width = width,
                    Height = height,
                    Grid = data.Skip(4).ToArray()
                });
                i++;
            }

            Log.Info($"[RESOURCE_LOADER] {maps.Count} Maps loaded");
            return maps;
        }

        private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath)
        {
            try
            {
                using var conn = OpenConnection();
                using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND relative_path=@path LIMIT 1;", conn);
                cmd.Parameters.AddWithValue("path", $"dat/{datFileName}");
                object result = cmd.ExecuteScalar();

                if (result is byte[] bytes && bytes.Length > 0)
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _config.GameDataPath);
                    File.WriteAllBytes(targetPath, bytes);
                    Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files");
                }
            }
            catch (Exception ex)
            {
                Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex);
            }
        }

        private void TryHydrateMapFilesFromDatabase()
        {
            try
            {
                Directory.CreateDirectory(_config.GameMapsPath);
                using var conn = OpenConnection();
                using var cmd = new NpgsqlCommand("SELECT relative_path, content FROM resource_files WHERE category='maps';", conn);
                using var reader = cmd.ExecuteReader();
                int count = 0;
                while (reader.Read())
                {
                    string relative = reader.GetString(0).Replace('/', Path.DirectorySeparatorChar);
                    byte[] content = (byte[])reader[1];
                    string path = Path.Combine(_config.ResourcePaths, relative);
                    Directory.CreateDirectory(Path.GetDirectoryName(path) ?? _config.GameMapsPath);
                    File.WriteAllBytes(path, content);
                    count++;
                }

                if (count > 0)
                {
                    Log.Info($"[DB_FIRST] Hydrated {count} map files from resource_files");
                }
            }
            catch (Exception ex)
            {
                Log.Error("[DB_FIRST] Could not hydrate map files from database", ex);
            }
        }

        private static NpgsqlConnection OpenConnection()
        {
            string host = Environment.GetEnvironmentVariable("DATABASE_IP") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP") ?? "127.0.0.1";
            string port = Environment.GetEnvironmentVariable("DATABASE_PORT") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT") ?? "5432";
            string db = Environment.GetEnvironmentVariable("DATABASE_NAME") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME") ?? "game";
            string user = Environment.GetEnvironmentVariable("DATABASE_USER") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER") ?? "postgres";
            string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD") ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD") ?? "postgres";

            var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
            conn.Open();
            return conn;
        }
    }
}