using RolemasterDb.ImportTool.Parsing;

namespace RolemasterDb.ImportTool;

public sealed class CriticalImportCommandRunner
{
    private readonly CriticalImportManifestLoader manifestLoader = new();
    private readonly ImportArtifactWriter artifactWriter = new();
    private readonly PdfXmlExtractor pdfXmlExtractor = new();
    private readonly StandardOcrBootstrapper standardOcrBootstrapper = new();
    private readonly CriticalSourceImageArtifactGenerator sourceImageArtifactGenerator;
    private readonly CriticalSourceImageArtifactRegenerator sourceImageArtifactRegenerator;
    private readonly StandardCriticalTableParser standardParser = new();
    private readonly VariantColumnCriticalTableParser variantColumnParser = new();
    private readonly GroupedVariantCriticalTableParser groupedVariantParser = new();

    public CriticalImportCommandRunner()
    {
        sourceImageArtifactGenerator = new CriticalSourceImageArtifactGenerator(pdfXmlExtractor);
        sourceImageArtifactRegenerator = new CriticalSourceImageArtifactRegenerator(pdfXmlExtractor);
    }

    public async Task<int> RunAsync(ResetOptions options)
    {
        if (!string.Equals(options.Target, "criticals", StringComparison.OrdinalIgnoreCase))
        {
            Console.Error.WriteLine("Only 'criticals' is supported by phase 1.");
            return 1;
        }

        var loader = new CriticalImportLoader(ResolveDatabasePath(options.DatabasePath));
        var removedTableCount = await loader.ResetCriticalsAsync();
        Console.WriteLine($"Removed {removedTableCount} critical table records.");
        return 0;
    }

    public async Task<int> RunAsync(ExtractOptions options)
    {
        var entry = GetManifestEntry(options.Table);
        var artifactPaths = CreateArtifactPaths(entry.Slug);
        var extractor = CreateSourceExtractor(entry);
        await extractor.ExtractAsync(ResolveRepositoryPath(entry.PdfPath), artifactPaths, CancellationToken.None);
        Console.WriteLine($"Extracted {entry.Slug} to {artifactPaths.GetSourceArtifactPath(entry.ExtractionMethod)}");
        return 0;
    }

    public async Task<int> RunAsync(LoadOptions options)
    {
        var entry = GetManifestEntry(options.Table);
        var artifactPaths = CreateArtifactPaths(entry.Slug);
        var extractedSource = await LoadExtractedSourceAsync(entry, artifactPaths);
        var parseResult = Parse(entry, extractedSource);
        await sourceImageArtifactGenerator.GenerateAsync(
            ResolveRepositoryPath(entry.PdfPath),
            artifactPaths,
            parseResult,
            CancellationToken.None);
        await artifactWriter.WriteAsync(artifactPaths, parseResult, CancellationToken.None);

        if (!parseResult.ValidationReport.IsValid)
        {
            throw new InvalidOperationException(
                $"Validation failed for '{entry.Slug}'. See {artifactPaths.ValidationReportPath} for details.");
        }

        foreach (var warning in parseResult.ValidationReport.Warnings)
        {
            Console.WriteLine($"Warning: {warning}");
        }

        var loader = new CriticalImportLoader(ResolveDatabasePath(options.DatabasePath));
        var result = await loader.LoadAsync(parseResult.Table);

        Console.WriteLine(
            $"Loaded {result.TableSlug}: {result.ColumnCount} columns, {result.RollBandCount} roll bands, {result.ResultCount} results.");

        return 0;
    }

    public async Task<int> RunAsync(ImportOptions options)
    {
        var extractExitCode = await RunAsync(new ExtractOptions
        {
            DatabasePath = options.DatabasePath,
            Table = options.Table
        });

        if (extractExitCode != 0)
        {
            return extractExitCode;
        }

        return await RunAsync(new LoadOptions
        {
            DatabasePath = options.DatabasePath,
            Table = options.Table
        });
    }

    public async Task<int> RunAsync(ReimportImagesOptions options)
    {
        var entries = GetManifestEntries(options).ToList();
        CriticalImportLoader? loader = null;
        if (options.UpdateMetadata)
        {
            loader = new CriticalImportLoader(ResolveDatabasePath(options.DatabasePath));
        }

        foreach (var entry in entries)
        {
            var artifactPaths = CreateArtifactPaths(entry.Slug);
            try
            {
                var extractor = CreateSourceExtractor(entry);
                if (!File.Exists(artifactPaths.GetSourceArtifactPath(entry.ExtractionMethod)))
                {
                    await extractor.ExtractAsync(ResolveRepositoryPath(entry.PdfPath), artifactPaths,
                        CancellationToken.None);
                }

                var extractedSource = await extractor.LoadAsync(ResolveRepositoryPath(entry.PdfPath), artifactPaths,
                    CancellationToken.None);
                var parseResult = Parse(entry, extractedSource);
                await sourceImageArtifactGenerator.GenerateAsync(
                    ResolveRepositoryPath(entry.PdfPath),
                    artifactPaths,
                    parseResult,
                    CancellationToken.None);
                await artifactWriter.WriteAsync(artifactPaths, parseResult, CancellationToken.None);

                if (!parseResult.ValidationReport.IsValid)
                {
                    throw new InvalidOperationException(
                        $"Validation failed for '{entry.Slug}'. See {artifactPaths.ValidationReportPath} for details.");
                }

                if (loader is null)
                {
                    Console.WriteLine(
                        $"Refreshed image artifacts for {entry.Slug}: metadata unchanged.");
                    continue;
                }

                var refreshedCount = await loader.RefreshImageArtifactsAsync(parseResult.Table);
                Console.WriteLine(
                    $"Refreshed image artifacts for {entry.Slug}: {refreshedCount} results updated.");
            }
            catch (Exception exception) when (CanUseDatabaseMetadataFallback(entry, options))
            {
                Console.Error.WriteLine(
                    $"Falling back to database image metadata for {entry.Slug}: {exception.Message}");

                var metadataLoader = loader ?? new CriticalImportLoader(ResolveDatabasePath(options.DatabasePath));
                var imageArtifacts = await metadataLoader.LoadImageArtifactMetadataAsync(entry.Slug);
                await sourceImageArtifactRegenerator.GenerateAsync(
                    ResolveRepositoryPath(entry.PdfPath),
                    artifactPaths,
                    imageArtifacts,
                    CancellationToken.None);

                Console.WriteLine(
                    $"Refreshed image artifacts for {entry.Slug}: regenerated {imageArtifacts.Count} images from existing database metadata.");
            }
        }

        return 0;
    }

    private CriticalImportManifestEntry GetManifestEntry(string tableSlug)
    {
        var manifest = manifestLoader.Load(RepositoryPaths.Discover().ManifestPath);
        return manifest.Tables
                   .Where(item => item.Enabled)
                   .SingleOrDefault(item =>
                       string.Equals(item.Slug, tableSlug.Trim(), StringComparison.OrdinalIgnoreCase))
               ?? throw new InvalidOperationException($"No enabled manifest entry was found for '{tableSlug}'.");
    }

    private IReadOnlyList<CriticalImportManifestEntry> GetManifestEntries(ReimportImagesOptions options)
    {
        if (options.All)
        {
            var manifest = manifestLoader.Load(RepositoryPaths.Discover().ManifestPath);
            return manifest.Tables.Where(item => item.Enabled).ToList();
        }

        var normalizedTable = NormalizeTableArgument(options.Table);
        if (string.IsNullOrWhiteSpace(normalizedTable))
        {
            throw new InvalidOperationException("Specify a table slug or pass --all.");
        }

        return new[] { GetManifestEntry(normalizedTable) };
    }

    private static string? NormalizeTableArgument(string? table)
    {
        if (string.IsNullOrWhiteSpace(table))
        {
            return null;
        }

        var trimmed = table.Trim();
        return trimmed[0] == '-' ? null : trimmed;
    }

    private static bool
        CanUseDatabaseMetadataFallback(CriticalImportManifestEntry entry, ReimportImagesOptions options) =>
        !options.UpdateMetadata
        && string.Equals(entry.ExtractionMethod, "ocr", StringComparison.OrdinalIgnoreCase);

    private async Task<ExtractedCriticalSource> LoadExtractedSourceAsync(CriticalImportManifestEntry entry,
        ImportArtifactPaths artifactPaths)
    {
        var extractor = CreateSourceExtractor(entry);
        var sourceArtifactPath = artifactPaths.GetSourceArtifactPath(entry.ExtractionMethod);
        if (!File.Exists(sourceArtifactPath))
        {
            Console.Error.WriteLine($"Missing source artifact: {sourceArtifactPath}");
            throw new FileNotFoundException($"Missing source artifact: {sourceArtifactPath}", sourceArtifactPath);
        }

        return await extractor.LoadAsync(ResolveRepositoryPath(entry.PdfPath), artifactPaths, CancellationToken.None);
    }

    private CriticalTableParseResult Parse(CriticalImportManifestEntry entry, ExtractedCriticalSource source)
    {
        if (string.Equals(entry.Family, "standard", StringComparison.OrdinalIgnoreCase))
        {
            if (string.Equals(entry.ExtractionMethod, "ocr", StringComparison.OrdinalIgnoreCase))
            {
                var template = StandardTableAxisTemplateCatalog.Resolve(entry.AxisTemplateSlug);
                var layout = standardOcrBootstrapper.Bootstrap(source, template);
                return standardParser.Parse(entry, source, layout);
            }

            return standardParser.Parse(entry, source);
        }

        if (string.Equals(entry.Family, "variant_column", StringComparison.OrdinalIgnoreCase))
        {
            return variantColumnParser.Parse(entry, source);
        }

        if (string.Equals(entry.Family, "grouped_variant", StringComparison.OrdinalIgnoreCase))
        {
            return groupedVariantParser.Parse(entry, source);
        }

        throw new InvalidOperationException($"Family '{entry.Family}' is not supported by the importer.");
    }

    private ICriticalSourceExtractor CreateSourceExtractor(CriticalImportManifestEntry entry)
    {
        if (string.Equals(entry.ExtractionMethod, "xml", StringComparison.OrdinalIgnoreCase))
        {
            return new XmlCriticalSourceExtractor(pdfXmlExtractor);
        }

        if (string.Equals(entry.ExtractionMethod, "ocr", StringComparison.OrdinalIgnoreCase))
        {
            return new OcrCriticalSourceExtractor(pdfXmlExtractor);
        }

        throw new InvalidOperationException(
            $"Extraction method '{entry.ExtractionMethod}' is not supported by the importer.");
    }

    private static ImportArtifactPaths CreateArtifactPaths(string slug) =>
        ImportArtifactPaths.Create(RepositoryPaths.Discover().ArtifactsRootPath, slug);

    private static string ResolveDatabasePath(string? databasePath) =>
        Path.GetFullPath(databasePath ?? RepositoryPaths.Discover().DefaultDatabasePath);

    private static string ResolveRepositoryPath(string path) =>
        Path.GetFullPath(Path.Combine(RepositoryPaths.Discover().RootPath, path));
}