using System.Text.Json;
using RelayServer.Models;
using RelayServer.Services.Chat;
using RelayServer.Services.Crypto;
using SurrealDb.Net;

namespace RelayServer.Services.Core;

public sealed class ServerBootstrapService
{
    // TODO: Make channels dynamically addable
    // TODO: Add logic for channel types (ENUM)
    // TODO: Add logic for channel groups for future UI use
    
    private readonly SurrealDbClient _db;
    private readonly CoreClientService _coreClient;
    private readonly ChannelCryptoService _cryptoService;

    public ServerBootstrapService(
        SurrealDbClient db,
        CoreClientService coreClient,
        ChannelCryptoService cryptoService)
    {
        _db = db;
        _coreClient = coreClient;
        _cryptoService = cryptoService;
    }

    public async Task InitializeAsync()
    {
        var keeper = await _coreClient.GetUserByUsernameAsync("Keeper317");
        var kira = await _coreClient.GetUserByUsernameAsync("Ru_Kira");
        var test = await _coreClient.GetUserByUsernameAsync("Test");

        if (keeper is null || kira is null || test is null)
            throw new InvalidOperationException("One or more required users do not exist in RelayCore.");

        if (!keeper.Licensed || !kira.Licensed || !test.Licensed)
            throw new InvalidOperationException("One or more required users are not licensed.");

        Console.WriteLine($"Core verified user: {keeper.Username}");
        Console.WriteLine($"Core verified user: {kira.Username}");
        Console.WriteLine($"Core verified user: {test.Username}");

        var server = await GetServerByNameAsync("Test Server");

        if (server is null)
        {
            server = await _db.Create("servers", new Servers
            {
                Name = "Test Server",
                OwnerUserId = keeper.Id,
                CreatedAt = DateTime.UtcNow
            });

            Console.WriteLine($"Server created: {ToJsonString(server)}");
        }
        else
        {
            Console.WriteLine($"Server already exists: {ToJsonString(server)}");
        }

        await EnsureServerMemberAsync(keeper.Id, true);
        await EnsureServerMemberAsync(kira.Id, false);
        await EnsureServerMemberAsync(test.Id, false);

        Console.WriteLine("Server members ensured.");

        var channel = await EnsureChannelAsync("general", DateTime.UtcNow);
        var channel2 = await EnsureChannelAsync("files", DateTime.UtcNow.Subtract(new TimeSpan(0, 4, 0, 0)));
        var channel3 = await EnsureChannelAsync("welcome", DateTime.UtcNow.Subtract(new TimeSpan(1, 4, 4, 4)));
        var channel4 = await EnsureChannelAsync("voice-general", DateTime.UtcNow.Subtract(new TimeSpan(0, 2, 0, 0)));

        Console.WriteLine($"Resolved channelId: {GetRecordId(channel.Id)}");
        Console.WriteLine($"Resolved channelId: {GetRecordId(channel2.Id)}");
        Console.WriteLine($"Resolved channelId: {GetRecordId(channel3.Id)}");
        Console.WriteLine($"Resolved channelId: {GetRecordId(channel4.Id)}");

        var existingKey = await GetLatestServerEncryptionKeyAsync();

        if (existingKey is null)
        {
            var keyBase64 = _cryptoService.GenerateKey();
            var serverKeys = E2EeHelper.GenerateRsaKeyPair();

            existingKey = await _db.Create("server_encryption_keys", new ServerEncryptionKeys
            {
                KeyBase64 = keyBase64,
                PublicKey = serverKeys.publicKey,
                PrivateKey = serverKeys.privateKey,
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTime.UtcNow
            });

            Console.WriteLine("Server encryption key created.");
        }
        else
        {
            Console.WriteLine("Server encryption key already exists.");
        }

        ChatSocketBehavior.ServerPublicKey = existingKey.PublicKey;
        ChatSocketBehavior.ServerPrivateKey = existingKey.PrivateKey;
        ChatSocketBehavior.ChannelDbKey = existingKey.KeyBase64;
    }

    private static string ToJsonString(object? obj)
    {
        return JsonSerializer.Serialize(obj, new JsonSerializerOptions
        {
            WriteIndented = true,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        });
    }

    private static string GetRecordId(object? id)
    {
        if (id is null)
            return string.Empty;

        var json = JsonSerializer.Serialize(id);

        using var doc = JsonDocument.Parse(json);
        var root = doc.RootElement;

        var recordId = root.GetProperty("Id").GetString() ?? string.Empty;
        var table = root.GetProperty("Table").GetString() ?? string.Empty;

        return $"{table}:{recordId}";
    }
    
    private async Task<Servers?> GetServerByNameAsync(string name)
    {
        var servers = await _db.Select<Servers>("servers");
        return servers.FirstOrDefault(x => x.Name == name);
    }

    private async Task<ServerMembers?> GetServerMemberByUserIdAsync(string userId)
    {
        var members = await _db.Select<ServerMembers>("server_members");
        return members.FirstOrDefault(x => x.UserId == userId);
    }

    private async Task<Channels?> GetChannelByNameAsync(string name)
    {
        var channels = await _db.Select<Channels>("channels");
        return channels.FirstOrDefault(x => x.Name == name);
    }

    private async Task<ServerEncryptionKeys?> GetLatestServerEncryptionKeyAsync()
    {
        var keys = await _db.Select<ServerEncryptionKeys>("server_encryption_keys");
        return keys
            .OrderByDescending(x => x.CreatedAt)
            .FirstOrDefault();
    }
    
    private async Task EnsureServerMemberAsync(string userId, bool isOwner)
    {
        var existing = await GetServerMemberByUserIdAsync(userId);
        if (existing is not null)
        {
            Console.WriteLine($"Server member already exists for {userId}");
            return;
        }

        await _db.Create("server_members", new ServerMembers
        {
            UserId = userId,
            JoinedAt = DateTime.UtcNow,
            IsOwner = isOwner
        });

        Console.WriteLine($"Server member created for {userId}");
    }
    
    private async Task<Channels> EnsureChannelAsync(string name, DateTime createdAt)
    {
        var existing = await GetChannelByNameAsync(name);
        if (existing is not null)
        {
            Console.WriteLine($"Channel already exists: {name}");
            return existing;
        }

        var channel = await _db.Create("channels", new Channels
        {
            Name = name,
            CreatedAt = createdAt
        });

        Console.WriteLine($"Channel created: {ToJsonString(channel)}");
        return channel;
    }
}