using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using Couchbase.Lite;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using WorkTracker.Configuration;
using WorkTracker.Domain;
using WorkTracker.Services.Settings;
using WorkTracker.Services.Storage;
using Xunit;

namespace WorkTracker.Tests;

public sealed class JsonDatabaseBackupServiceTests
{
    [Fact]
    public async Task ExportAndImport_RoundTripsManagedCollectionsAndVersions()
    {
        using var sourceDatabase = new TestDatabaseHandle();
        await SeedAsync(sourceDatabase.Provider);

        var backupService = new JsonDatabaseBackupService(sourceDatabase.Provider);
        var backupFile = await backupService.ExportAsync();

        using var backupJson = JsonDocument.Parse(backupFile.Content);
        Assert.Equal(1, backupJson.RootElement.GetProperty("backupFormatVersion").GetInt32());
        Assert.Equal(CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion, backupJson.RootElement.GetProperty("databaseSchemaVersion").GetInt32());
        Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("app_settings", out _));
        Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("users", out _));
        Assert.True(backupJson.RootElement.GetProperty("collections").TryGetProperty("workdays", out _));

        using var targetDatabase = new TestDatabaseHandle();
        var importService = new JsonDatabaseBackupService(targetDatabase.Provider);

        await using (var stream = new MemoryStream(backupFile.Content, writable: false))
        {
            await importService.ImportAsync(stream);
        }

        var appSettingsService = new CouchbaseLiteAppSettingsService(targetDatabase.Provider);
        var importedSettings = await appSettingsService.GetAsync();

        Assert.Equal(AppThemeMode.Dark, importedSettings.ThemeMode);
        Assert.Equal("USD", importedSettings.Currency);
        Assert.Equal("en-US", importedSettings.Locale);
        Assert.Equal(CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion, targetDatabase.Provider.DatabaseSchemaVersion);

        var importedUser = targetDatabase.Provider.Users.GetDocument("USER1");
        Assert.NotNull(importedUser);
        Assert.Equal("admin", importedUser!.GetString("username"));

        var importedWorkDay = targetDatabase.Provider.WorkDays.GetDocument("2026-04-24");
        Assert.NotNull(importedWorkDay);
        Assert.Equal("2026-04-24", importedWorkDay!.GetString("date"));
    }

    [Fact]
    public async Task Import_WhenSchemaVersionDoesNotMatch_RejectsWithoutOverwritingDatabase()
    {
        using var sourceDatabase = new TestDatabaseHandle();
        await SeedAsync(sourceDatabase.Provider);

        var backupService = new JsonDatabaseBackupService(sourceDatabase.Provider);
        var backupFile = await backupService.ExportAsync();

        var modifiedBackup = JsonNode.Parse(backupFile.Content)!;
        modifiedBackup["databaseSchemaVersion"] = CouchbaseLiteDatabaseProvider.CurrentDatabaseSchemaVersion + 1;
        var modifiedContent = Encoding.UTF8.GetBytes(modifiedBackup.ToJsonString(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));

        using var targetDatabase = new TestDatabaseHandle();
        SeedExistingTargetData(targetDatabase.Provider);
        var importService = new JsonDatabaseBackupService(targetDatabase.Provider);

        await using var stream = new MemoryStream(modifiedContent, writable: false);
        var exception = await Assert.ThrowsAsync<DatabaseBackupException>(() => importService.ImportAsync(stream));

        Assert.Contains("not supported", exception.Message, StringComparison.OrdinalIgnoreCase);
        Assert.NotNull(targetDatabase.Provider.Users.GetDocument("KEEP"));
        Assert.Null(targetDatabase.Provider.Users.GetDocument("USER1"));
    }

    private static async Task SeedAsync(CouchbaseLiteDatabaseProvider provider)
    {
        var appSettingsService = new CouchbaseLiteAppSettingsService(provider);
        await appSettingsService.SaveAsync(new AppSettingsDocument
        {
            ThemeMode = AppThemeMode.Dark,
            Currency = "USD",
            Locale = "en-US",
            StandardWorkHoursPerDay = 7.5m,
            HourlyGrossRate = 30m,
            ProfitabilityCoefficient = 0.8m,
            InpsRate = 0.2m,
            SubstituteTaxRate = 0.1m
        });

        var userDocument = new MutableDocument("USER1");
        userDocument.SetString("username", "admin");
        userDocument.SetString("usernameNormalized", "ADMIN");
        userDocument.SetString("passwordHash", "hash");
        userDocument.SetBoolean("mustChangePassword", false);
        provider.Users.Save(userDocument);

        var workDayDocument = new MutableDocument("2026-04-24");
        workDayDocument.SetString("date", "2026-04-24");
        workDayDocument.SetBoolean("isWeekend", false);
        workDayDocument.SetBoolean("isItalianFestivity", false);
        workDayDocument.SetArray("workUnits", new MutableArrayObject());
        workDayDocument.SetArray("calendarEvents", new MutableArrayObject());
        workDayDocument.SetString("createdAtUtc", "2026-04-24T08:00:00.0000000+00:00");
        workDayDocument.SetString("updatedAtUtc", "2026-04-24T08:00:00.0000000+00:00");
        provider.WorkDays.Save(workDayDocument);
    }

    private static void SeedExistingTargetData(CouchbaseLiteDatabaseProvider provider)
    {
        var existingUser = new MutableDocument("KEEP");
        existingUser.SetString("username", "keep-me");
        existingUser.SetString("usernameNormalized", "KEEP-ME");
        existingUser.SetString("passwordHash", "keep-hash");
        existingUser.SetBoolean("mustChangePassword", false);
        provider.Users.Save(existingUser);
    }

    private sealed class TestDatabaseHandle : IDisposable
    {
        private readonly string directoryPath;

        public TestDatabaseHandle()
        {
            directoryPath = Path.Combine(Path.GetTempPath(), "WorkTracker.Tests", Guid.NewGuid().ToString("N"));
            Directory.CreateDirectory(directoryPath);

            Provider = new CouchbaseLiteDatabaseProvider(
                Options.Create(new CouchbaseLiteOptions
                {
                    Directory = directoryPath,
                    DatabaseName = $"worktracker-tests-{Guid.NewGuid():N}"
                }),
                new TestHostEnvironment());
        }

        public CouchbaseLiteDatabaseProvider Provider { get; }

        public void Dispose()
        {
            Provider.Dispose();

            if (Directory.Exists(directoryPath))
            {
                Directory.Delete(directoryPath, recursive: true);
            }
        }
    }

    private sealed class TestHostEnvironment : IHostEnvironment
    {
        public string EnvironmentName { get; set; } = "Development";

        public string ApplicationName { get; set; } = "WorkTracker.Tests";

        public string ContentRootPath { get; set; } = Directory.GetCurrentDirectory();

        public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!;
    }
}