using Microsoft.EntityFrameworkCore;
using System.Text.Json;

using RolemasterDb.App.Data;
using RolemasterDb.App.Domain;
using RolemasterDb.ImportTool.Parsing;

namespace RolemasterDb.ImportTool;

public sealed class CriticalImportLoader(string databasePath)
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = true
    };

    public async Task<int> ResetCriticalsAsync(CancellationToken cancellationToken = default)
    {
        await using var dbContext = CreateDbContext();
        await dbContext.Database.EnsureCreatedAsync(cancellationToken);
        await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext, cancellationToken);

        var removedTableCount = await dbContext.CriticalTables.CountAsync(cancellationToken);
        await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);

        await dbContext.CriticalEffects.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalBranches.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalResults.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalGroups.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalColumns.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalRollBands.ExecuteDeleteAsync(cancellationToken);
        await dbContext.CriticalTables.ExecuteDeleteAsync(cancellationToken);

        await transaction.CommitAsync(cancellationToken);
        return removedTableCount;
    }

    public async Task<ImportCommandResult> LoadAsync(ParsedCriticalTable table, CancellationToken cancellationToken = default)
    {
        await using var dbContext = CreateDbContext();
        await dbContext.Database.EnsureCreatedAsync(cancellationToken);
        await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext, cancellationToken);
        await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);

        await DeleteTableAsync(dbContext, table.Slug, cancellationToken);

        var entity = new CriticalTable
        {
            Slug = table.Slug,
            DisplayName = table.DisplayName,
            Family = table.Family,
            SourceDocument = table.SourceDocument,
            Notes = table.Notes
        };

        entity.Groups = table.Groups
            .Select(item => new CriticalGroup
            {
                GroupKey = item.GroupKey,
                Label = item.Label,
                SortOrder = item.SortOrder
            })
            .ToList();

        entity.Columns = table.Columns
            .Select(item => new CriticalColumn
            {
                ColumnKey = item.ColumnKey,
                Label = item.Label,
                Role = item.Role,
                SortOrder = item.SortOrder
            })
            .ToList();

        entity.RollBands = table.RollBands
            .Select(item => new CriticalRollBand
            {
                Label = item.Label,
                MinRoll = item.MinRoll,
                MaxRoll = item.MaxRoll,
                SortOrder = item.SortOrder
            })
            .ToList();

        var groupsByKey = entity.Groups.ToDictionary(item => item.GroupKey, StringComparer.OrdinalIgnoreCase);
        var columnsByKey = entity.Columns.ToDictionary(item => item.ColumnKey, StringComparer.OrdinalIgnoreCase);
        var rollBandsByLabel = entity.RollBands.ToDictionary(item => item.Label, StringComparer.OrdinalIgnoreCase);

        entity.Results = table.Results
            .Select(item => new CriticalResult
            {
                CriticalGroup = item.GroupKey is null ? null : groupsByKey[item.GroupKey],
                CriticalColumn = columnsByKey[item.ColumnKey],
                CriticalRollBand = rollBandsByLabel[item.RollBandLabel],
                RawCellText = item.RawCellText,
                DescriptionText = item.DescriptionText,
                RawAffixText = item.RawAffixText,
                ParsedJson = SerializeParsedEffects(item.Effects),
                ParseStatus = ResolveParseStatus(item.Effects, item.Branches),
                Effects = item.Effects
                    .Select(CreateEffectEntity)
                    .ToList(),
                Branches = item.Branches
                    .Select(branch => new CriticalBranch
                    {
                        BranchKind = branch.BranchKind,
                        ConditionKey = branch.ConditionKey,
                        ConditionText = branch.ConditionText,
                        ConditionJson = "{}",
                        RawText = branch.RawText,
                        DescriptionText = branch.DescriptionText,
                        RawAffixText = branch.RawAffixText,
                        ParsedJson = SerializeParsedEffects(branch.Effects),
                        SortOrder = branch.SortOrder,
                        Effects = branch.Effects
                            .Select(CreateEffectEntity)
                            .ToList()
                    })
                    .ToList()
            })
            .ToList();

        dbContext.CriticalTables.Add(entity);
        await dbContext.SaveChangesAsync(cancellationToken);
        await transaction.CommitAsync(cancellationToken);

        return new ImportCommandResult(entity.Slug, entity.Columns.Count, entity.RollBands.Count, entity.Results.Count);
    }

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

        return new RolemasterDbContext(options);
    }

    private static async Task DeleteTableAsync(
        RolemasterDbContext dbContext,
        string slug,
        CancellationToken cancellationToken)
    {
        var tableId = await dbContext.CriticalTables
            .Where(item => item.Slug == slug)
            .Select(item => (int?)item.Id)
            .SingleOrDefaultAsync(cancellationToken);

        if (tableId is null)
        {
            return;
        }

        await dbContext.CriticalEffects
            .Where(item => item.CriticalBranch != null && item.CriticalBranch.CriticalResult.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalEffects
            .Where(item => item.CriticalResult != null && item.CriticalResult.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalBranches
            .Where(item => item.CriticalResult.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalResults
            .Where(item => item.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalGroups
            .Where(item => item.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalColumns
            .Where(item => item.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalRollBands
            .Where(item => item.CriticalTableId == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);

        await dbContext.CriticalTables
            .Where(item => item.Id == tableId.Value)
            .ExecuteDeleteAsync(cancellationToken);
    }

    private static CriticalEffect CreateEffectEntity(ParsedCriticalEffect effect) =>
        new()
        {
            EffectCode = effect.EffectCode,
            Target = effect.Target,
            ValueInteger = effect.ValueInteger,
            ValueExpression = effect.ValueExpression,
            DurationRounds = effect.DurationRounds,
            PerRound = effect.PerRound,
            Modifier = effect.Modifier,
            BodyPart = effect.BodyPart,
            IsPermanent = effect.IsPermanent,
            SourceType = effect.SourceType,
            SourceText = effect.SourceText
        };

    private static string SerializeParsedEffects(IReadOnlyList<ParsedCriticalEffect> effects) =>
        effects.Count == 0
            ? "{}"
            : JsonSerializer.Serialize(new { effects }, JsonOptions);

    private static string ResolveParseStatus(
        IReadOnlyList<ParsedCriticalEffect> effects,
        IReadOnlyList<ParsedCriticalBranch> branches) =>
        effects.Count > 0 || branches.Any(branch => branch.Effects.Count > 0)
            ? "partial"
            : "raw";
}
