using Microsoft.EntityFrameworkCore;
using RolemasterDb.App.Data;
using RolemasterDb.App.Domain;
using RolemasterDb.App.Features;

namespace RolemasterDb.ImportTool.Tests;

public sealed class LookupServiceCurationIntegrationTests
{
    [Fact]
    public async Task Lookup_service_surfaces_and_persists_curated_state_and_source_image_metadata()
    {
        var databasePath = CreateEmptyDatabasePath();
        var repositoryRoot = CreateTemporaryRepositoryRoot();
        var locator = new CriticalImportArtifactLocator(new TestHostEnvironment(Path.Combine(repositoryRoot, "src", "RolemasterDb.App")));

        await SeedCriticalResultAsync(databasePath, "slash/cells/source-cell.png", 2);
        WriteSourceImage(repositoryRoot, "slash/cells/source-cell.png");

        var lookupService = new LookupService(CreateDbContextFactory(databasePath), locator);
        var resultId = await GetResultIdAsync(databasePath);

        var initialResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(initialResponse);
        Assert.False(initialResponse!.IsCurated);
        Assert.Equal(2, initialResponse.SourcePageNumber);
        Assert.StartsWith($"/api/tables/critical/slash/cells/{resultId}/source-image?v=", initialResponse.SourceImageUrl, StringComparison.Ordinal);

        var updateRequest = new CriticalCellUpdateRequest(initialResponse.RawCellText, initialResponse.QuickParseInput, initialResponse.DescriptionText, initialResponse.RawAffixText, initialResponse.ParseStatus, initialResponse.ParsedJson, true, initialResponse.IsDescriptionOverridden, initialResponse.IsRawAffixTextOverridden, initialResponse.AreEffectsOverridden, initialResponse.AreBranchesOverridden, initialResponse.Effects, initialResponse.Branches);

        var updatedResponse = await lookupService.UpdateCriticalCellAsync("slash", resultId, updateRequest);
        Assert.NotNull(updatedResponse);
        Assert.True(updatedResponse!.IsCurated);

        var tableDetail = await lookupService.GetCriticalTableAsync("slash");
        Assert.NotNull(tableDetail);
        Assert.Contains(tableDetail!.Cells, item => item.ResultId == resultId && item.IsCurated);

        var reopenedResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(reopenedResponse);
        Assert.True(reopenedResponse!.IsCurated);
        Assert.Equal(initialResponse.SourceImageUrl, reopenedResponse.SourceImageUrl);
    }

    [Fact]
    public async Task Lookup_service_changes_source_image_url_when_source_provenance_changes()
    {
        var databasePath = CreateEmptyDatabasePath();
        var repositoryRoot = CreateTemporaryRepositoryRoot();
        var locator = new CriticalImportArtifactLocator(new TestHostEnvironment(Path.Combine(repositoryRoot, "src", "RolemasterDb.App")));

        await SeedCriticalResultAsync(databasePath, "slash/cells/source-cell.png", 2);
        WriteSourceImage(repositoryRoot, "slash/cells/source-cell.png");
        WriteSourceImage(repositoryRoot, "slash/cells/source-cell-v2.png");

        var lookupService = new LookupService(CreateDbContextFactory(databasePath), locator);
        var resultId = await GetResultIdAsync(databasePath);

        var initialResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(initialResponse);
        Assert.StartsWith($"/api/tables/critical/slash/cells/{resultId}/source-image?v=", initialResponse!.SourceImageUrl, StringComparison.Ordinal);

        await UpdateSourceMetadataAsync(databasePath, resultId, sourcePageNumber: 4, sourceImagePath: "slash/cells/source-cell-v2.png", sourceImageCropJson: "{\"pageNumber\":4,\"boundsLeft\":12}");

        var reopenedResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(reopenedResponse);
        Assert.StartsWith($"/api/tables/critical/slash/cells/{resultId}/source-image?v=", reopenedResponse!.SourceImageUrl, StringComparison.Ordinal);
        Assert.NotEqual(initialResponse.SourceImageUrl, reopenedResponse.SourceImageUrl);
    }

    [Fact]
    public async Task Lookup_service_clears_curated_state_when_any_content_is_edited()
    {
        var databasePath = CreateEmptyDatabasePath();
        var repositoryRoot = CreateTemporaryRepositoryRoot();
        var locator = new CriticalImportArtifactLocator(new TestHostEnvironment(Path.Combine(repositoryRoot, "src", "RolemasterDb.App")));

        await SeedCriticalResultAsync(databasePath, "slash/cells/source-cell.png", 2);
        WriteSourceImage(repositoryRoot, "slash/cells/source-cell.png");

        var lookupService = new LookupService(CreateDbContextFactory(databasePath), locator);
        var resultId = await GetResultIdAsync(databasePath);

        var initialResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(initialResponse);

        var markCuratedRequest = new CriticalCellUpdateRequest(initialResponse!.RawCellText, initialResponse.QuickParseInput, initialResponse.DescriptionText, initialResponse.RawAffixText, initialResponse.ParseStatus, initialResponse.ParsedJson, true, initialResponse.IsDescriptionOverridden, initialResponse.IsRawAffixTextOverridden, initialResponse.AreEffectsOverridden, initialResponse.AreBranchesOverridden, initialResponse.Effects, initialResponse.Branches);

        var curatedResponse = await lookupService.UpdateCriticalCellAsync("slash", resultId, markCuratedRequest);
        Assert.NotNull(curatedResponse);
        Assert.True(curatedResponse!.IsCurated);

        var editedRequest = markCuratedRequest with { DescriptionText = "Edited description after curation." };

        var editedResponse = await lookupService.UpdateCriticalCellAsync("slash", resultId, editedRequest);
        Assert.NotNull(editedResponse);
        Assert.False(editedResponse!.IsCurated);

        var reopenedResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(reopenedResponse);
        Assert.False(reopenedResponse!.IsCurated);
        Assert.Equal("Edited description after curation.", reopenedResponse.DescriptionText);
    }

    [Fact]
    public async Task Lookup_service_allows_marking_uncurated_reparsed_content_as_curated()
    {
        var databasePath = CreateEmptyDatabasePath();
        var repositoryRoot = CreateTemporaryRepositoryRoot();
        var locator = new CriticalImportArtifactLocator(new TestHostEnvironment(Path.Combine(repositoryRoot, "src", "RolemasterDb.App")));

        await SeedCriticalResultAsync(databasePath, "slash/cells/source-cell.png", 2);
        WriteSourceImage(repositoryRoot, "slash/cells/source-cell.png");

        var lookupService = new LookupService(CreateDbContextFactory(databasePath), locator);
        var resultId = await GetResultIdAsync(databasePath);

        var initialResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(initialResponse);
        Assert.False(initialResponse!.IsCurated);

        var reparsedResponse = await lookupService.ReparseCriticalCellAsync("slash", resultId, new CriticalCellUpdateRequest(initialResponse.RawCellText, "Edited quick parse input.", initialResponse.DescriptionText, initialResponse.RawAffixText, initialResponse.ParseStatus, initialResponse.ParsedJson, initialResponse.IsCurated, initialResponse.IsDescriptionOverridden, initialResponse.IsRawAffixTextOverridden, initialResponse.AreEffectsOverridden, initialResponse.AreBranchesOverridden, initialResponse.Effects, initialResponse.Branches));

        Assert.NotNull(reparsedResponse);
        Assert.False(reparsedResponse!.IsCurated);

        var curatedResponse = await lookupService.UpdateCriticalCellAsync("slash", resultId, new CriticalCellUpdateRequest(reparsedResponse.RawCellText, reparsedResponse.QuickParseInput, reparsedResponse.DescriptionText, reparsedResponse.RawAffixText, reparsedResponse.ParseStatus, reparsedResponse.ParsedJson, true, reparsedResponse.IsDescriptionOverridden, reparsedResponse.IsRawAffixTextOverridden, reparsedResponse.AreEffectsOverridden, reparsedResponse.AreBranchesOverridden, reparsedResponse.Effects, reparsedResponse.Branches));

        Assert.NotNull(curatedResponse);
        Assert.True(curatedResponse!.IsCurated);
        Assert.Equal("Edited quick parse input.", curatedResponse.QuickParseInput);

        var reopenedResponse = await lookupService.GetCriticalCellEditorAsync("slash", resultId);
        Assert.NotNull(reopenedResponse);
        Assert.True(reopenedResponse!.IsCurated);
        Assert.Equal("Edited quick parse input.", reopenedResponse.QuickParseInput);
    }

    [Fact]
    public async Task Lookup_service_resolves_source_image_paths_only_when_artifacts_exist()
    {
        var databasePath = CreateEmptyDatabasePath();
        var repositoryRoot = CreateTemporaryRepositoryRoot();
        var locator = new CriticalImportArtifactLocator(new TestHostEnvironment(Path.Combine(repositoryRoot, "src", "RolemasterDb.App")));

        await SeedCriticalResultAsync(databasePath, "slash/cells/missing.png", 1);

        var lookupService = new LookupService(CreateDbContextFactory(databasePath), locator);
        var resultId = await GetResultIdAsync(databasePath);

        var missingPath = await lookupService.GetCriticalSourceImagePathAsync("slash", resultId);
        Assert.Null(missingPath);

        WriteSourceImage(repositoryRoot, "slash/cells/missing.png");

        var resolvedPath = await lookupService.GetCriticalSourceImagePathAsync("slash", resultId);
        Assert.NotNull(resolvedPath);
        Assert.True(File.Exists(resolvedPath));
    }

    private static async Task SeedCriticalResultAsync(string databasePath, string sourceImagePath, int sourcePageNumber)
    {
        await using var dbContext = CreateDbContext(databasePath);
        await dbContext.Database.EnsureCreatedAsync();
        await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext);

        var table = new CriticalTable
        {
            Slug = "slash",
            DisplayName = "Slash Critical Strike Table",
            Family = "standard",
            SourceDocument = "Slash.pdf"
        };
        var column = new CriticalColumn
        {
            CriticalTable = table,
            ColumnKey = "B",
            Label = "B",
            Role = "severity",
            SortOrder = 2
        };
        var rollBand = new CriticalRollBand
        {
            CriticalTable = table,
            Label = "36-40",
            MinRoll = 36,
            MaxRoll = 40,
            SortOrder = 8
        };

        var result = new CriticalResult
        {
            CriticalTable = table,
            CriticalColumn = column,
            CriticalRollBand = rollBand,
            IsCurated = false,
            RawCellText = "Imported raw cell text",
            DescriptionText = "Imported description",
            ParseStatus = "partial",
            ParsedJson = "{}",
            SourcePageNumber = sourcePageNumber,
            SourceImagePath = sourceImagePath,
            SourceImageCropJson = "{\"pageNumber\":1}"
        };

        result.Effects.Add(new CriticalEffect
        {
            EffectCode = CriticalEffectCodes.DirectHits,
            ValueInteger = 5,
            IsPermanent = false,
            SourceType = "symbol",
            SourceText = "+5H"
        });

        dbContext.CriticalTables.Add(table);
        dbContext.CriticalResults.Add(result);
        await dbContext.SaveChangesAsync();
    }

    private static void WriteSourceImage(string repositoryRoot, string relativePath)
    {
        var fullPath = Path.Combine(repositoryRoot, "artifacts", "import", "critical", relativePath.Replace('/', Path.DirectorySeparatorChar));
        Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
        File.WriteAllBytes(fullPath, [137, 80, 78, 71]);
    }

    private static async Task<int> GetResultIdAsync(string databasePath)
    {
        await using var dbContext = CreateDbContext(databasePath);
        return await dbContext.CriticalResults.Where(item => item.CriticalTable.Slug == "slash").Select(item => item.Id).SingleAsync();
    }

    private static async Task UpdateSourceMetadataAsync(string databasePath, int resultId, int sourcePageNumber, string sourceImagePath, string sourceImageCropJson)
    {
        await using var dbContext = CreateDbContext(databasePath);
        var result = await dbContext.CriticalResults.SingleAsync(item => item.Id == resultId);
        result.SourcePageNumber = sourcePageNumber;
        result.SourceImagePath = sourceImagePath;
        result.SourceImageCropJson = sourceImageCropJson;
        await dbContext.SaveChangesAsync();
    }

    private static RolemasterDbContext CreateDbContext(string databasePath)
    {
        var options = new DbContextOptionsBuilder<RolemasterDbContext>().UseSqlite($"Data Source={databasePath}").Options;

        return new RolemasterDbContext(options);
    }

    private static IDbContextFactory<RolemasterDbContext> CreateDbContextFactory(string databasePath)
    {
        var options = new DbContextOptionsBuilder<RolemasterDbContext>().UseSqlite($"Data Source={databasePath}").Options;

        return new TestRolemasterDbContextFactory(options);
    }

    private static string CreateEmptyDatabasePath() =>
        Path.Combine(Path.GetTempPath(), $"lookup-service-curation-{Guid.NewGuid():N}.db");

    private static string CreateTemporaryRepositoryRoot()
    {
        var repositoryRoot = Path.Combine(Path.GetTempPath(), $"rolemaster-repo-{Guid.NewGuid():N}");
        Directory.CreateDirectory(Path.Combine(repositoryRoot, "src", "RolemasterDb.App"));
        File.WriteAllText(Path.Combine(repositoryRoot, "RolemasterDB.slnx"), string.Empty);
        return repositoryRoot;
    }
}