using System.CommandLine;
using System.Reflection;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Data.Sqlite;
using Spectre.Console;

// Ensure UTF-8 output for Unicode characters
Console.OutputEncoding = Encoding.UTF8;

var dlensArgument = new Argument<FileInfo>("dlens");
dlensArgument.Description = "Path to the .dlens database file";

var outputOption = new Option<FileInfo?>("--output", "-o");
outputOption.Description = "Output CSV file path (defaults to collection.csv)";

var showTableOption = new Option<bool>("--show-table", "-t");
showTableOption.Description = "Display the card collection as a table";
showTableOption.DefaultValueFactory = _ => false;

#if EMBEDDED_APK
var rootCommand = new RootCommand("Extract and display card data from Delver Lens")
{
    dlensArgument,
    outputOption,
    showTableOption
};

rootCommand.SetAction(async (parseResult, cancellationToken) =>
{
    var dlensFile = parseResult.GetValue(dlensArgument)!;
    var outputFile = parseResult.GetValue(outputOption) ?? new FileInfo("collection.csv");
    var showTable = parseResult.GetValue(showTableOption);
    await ProcessFiles(null, dlensFile, outputFile, showTable);
});
#else
var apkArgument = new Argument<FileInfo>("apk");
apkArgument.Description = "Path to the Delver Lens APK file";

var rootCommand = new RootCommand("Extract and display card data from Delver Lens")
{
    apkArgument,
    dlensArgument,
    outputOption,
    showTableOption
};

rootCommand.SetAction(async (parseResult, cancellationToken) =>
{
    var apkFile = parseResult.GetValue(apkArgument)!;
    var dlensFile = parseResult.GetValue(dlensArgument)!;
    var outputFile = parseResult.GetValue(outputOption) ?? new FileInfo("collection.csv");
    var showTable = parseResult.GetValue(showTableOption);
    await ProcessFiles(apkFile, dlensFile, outputFile, showTable);
});
#endif

return await rootCommand.Parse(args).InvokeAsync();

async Task ProcessFiles(FileInfo? apkFile, FileInfo dlensFile, FileInfo outputFile, bool showTable)
{
#if !EMBEDDED_APK
    if (apkFile == null || !apkFile.Exists)
    {
        AnsiConsole.MarkupLine($"[red]APK file not found:[/] {apkFile?.FullName}");
        return;
    }
#endif

    if (!dlensFile.Exists)
    {
        AnsiConsole.MarkupLine($"[red]dlens file not found:[/] {dlensFile.FullName}");
        return;
    }

    List<ScannedCard>? scannedCards = null;
    List<CollectionCard>? collection = null;
    var steps = new[] { false, false, false };

    Panel BuildPanel()
    {
        var content = $"""
        [bold yellow]Progress[/]

        {Step(0, "Read scanned cards from dlens")}
        {Step(1, "Resolve card data from APK")}
        {Step(2, "Export collection to CSV")}
        """;

        if (steps[2])
        {
            content += $"""


            [bold yellow]Summary[/]

            [blue]Your collection:[/] {collection!.Count} unique cards, {collection.Sum(c => c.Quantity)} total
            [green]Exported to:[/] {outputFile.FullName}

            [bold yellow]How to import into Archidekt[/]

            1. Go to [link]https://archidekt.com/collection[/]
            2. Click [yellow]Import[/]
            3. Click [yellow]Add manual column[/] [blue]6 times[/]
            4. Set the columns in order:
               • Quantity → [blue]Quantity[/]
               • Scryfall ID → [blue]Scryfall ID[/]
               • Foil → [blue]Foil[/]
               • Card Name → [blue]Ignore[/]
               • Set Code → [blue]Ignore[/]
               • Collector Number → [blue]Ignore[/]
            5. Set [yellow]Skip first row[/] to [blue]true[/] [grey](the CSV has a header)[/]
            6. Set the csv file by either dragging and dropping it, or clicking the upload box
            7. Click [yellow]Upload[/]
            """;
        }

        return new Panel(content)
        {
            Header = new PanelHeader(" Delver Lens → Archidekt "),
            Border = BoxBorder.Rounded,
            Padding = new Padding(2, 1)
        };
    }

    var spinnerFrames = new[] { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" };
    var spinnerIndex = 0;
    var currentStep = 0;

    string Step(int index, string text)
    {
        if (steps[index])
            return $"[green][[✓]][/] {text}";
        if (index == currentStep)
            return $"[blue][[{spinnerFrames[spinnerIndex]}]][/] {text}";
        return $"[grey][[○]][/] [grey]{text}[/]";
    }

    // When piped, output CSV to stdout for composability
    if (Console.IsOutputRedirected)
    {
        scannedCards = await GetScannedCards(dlensFile);
        collection = await ResolveCollection(apkFile, scannedCards);
        WriteCsvToStdout(collection);
        return;
    }

    // Interactive: use live display with progress panel
    using var cts = new CancellationTokenSource();

    await AnsiConsole.Live(BuildPanel())
        .StartAsync(async ctx =>
        {
            // Spinner animation task
            var spinnerTask = Task.Run(async () =>
            {
                while (!cts.Token.IsCancellationRequested)
                {
                    await Task.Delay(80, cts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
                    spinnerIndex = (spinnerIndex + 1) % spinnerFrames.Length;
                    ctx.UpdateTarget(BuildPanel());
                }
            });

            scannedCards = await GetScannedCards(dlensFile);
            steps[0] = true;
            currentStep = 1;
            ctx.UpdateTarget(BuildPanel());

            collection = await ResolveCollection(apkFile, scannedCards);
            steps[1] = true;
            currentStep = 2;
            ctx.UpdateTarget(BuildPanel());

            await ExportCsv(collection, outputFile);
            steps[2] = true;
            ctx.UpdateTarget(BuildPanel());

            cts.Cancel();
            await spinnerTask;
        });

    // Display table if requested (after live panel completes)
    if (showTable)
    {
        DisplayCollection(collection!);
    }
}

async Task<List<CollectionCard>> ResolveCollection(FileInfo? apkFile, List<ScannedCard> scannedCards)
{
    var tempDbPath = Path.GetTempFileName();
    var cardIds = scannedCards.Select(c => c.CardId).ToHashSet();

    try
    {
        // Get APK stream from embedded resource or file
#if EMBEDDED_APK
        var assembly = Assembly.GetExecutingAssembly();
        await using var apkStream = assembly.GetManifestResourceStream("delver.apk")
            ?? throw new Exception("Embedded APK resource not found");
#else
        await using var apkStream = apkFile!.OpenRead();
#endif

        using (var zipFile = new ZipFile(apkStream))
        {
            var entry = zipFile.GetEntry("res/raw/data.db");
            if (entry == null)
            {
                throw new Exception("Could not find res/raw/data.db in APK");
            }

            await using var zipStream = zipFile.GetInputStream(entry);
            await using var outputStream = File.Create(tempDbPath);
            await zipStream.CopyToAsync(outputStream);
        }

        var cardData = new Dictionary<int, (string Name, string SetCode, string CollectorNumber, string ScryfallId)>();

        await using (var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"))
        {
            await connection.OpenAsync();

            await using var cmd = connection.CreateCommand();
            cmd.CommandText = @"
                SELECT
                    c._id,
                    n.name,
                    e.tl_abb,
                    c.number,
                    c.scryfall_id
                FROM cards c
                JOIN names n ON c.name = n._id
                JOIN editions e ON c.edition = e._id;";

            await using var reader = await cmd.ExecuteReaderAsync();
            while (await reader.ReadAsync())
            {
                var id = reader.GetInt32(0);
                if (cardIds.Contains(id))
                {
                    cardData[id] = (
                        reader.GetString(1),
                        reader.GetString(2),
                        reader.IsDBNull(3) ? "" : reader.GetString(3),
                        reader.IsDBNull(4) ? "" : reader.GetString(4)
                    );
                }
            }
        }

        var collection = new List<CollectionCard>();
        foreach (var scanned in scannedCards)
        {
            if (cardData.TryGetValue(scanned.CardId, out var data))
            {
                collection.Add(new CollectionCard(
                    scanned.Quantity,
                    data.Name,
                    data.SetCode,
                    data.CollectorNumber,
                    data.ScryfallId,
                    scanned.Foil
                ));
            }
            else
            {
                collection.Add(new CollectionCard(
                    scanned.Quantity,
                    $"Unknown (ID: {scanned.CardId})",
                    "",
                    "",
                    "",
                    scanned.Foil
                ));
            }
        }

        return collection;
    }
    finally
    {
        SqliteConnection.ClearAllPools();
        if (File.Exists(tempDbPath))
        {
            File.Delete(tempDbPath);
        }
    }
}

void DisplayCollection(List<CollectionCard> collection)
{
    var table = new Table();
    table.Border = TableBorder.Rounded;
    table.AddColumn("Qty");
    table.AddColumn("Name");
    table.AddColumn("Set");
    table.AddColumn("#");
    table.AddColumn("Foil");
    table.AddColumn("Scryfall ID");

    foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode))
    {
        table.AddRow(
            card.Quantity.ToString(),
            card.Name.Length > 30 ? card.Name[..27] + "..." : card.Name,
            card.SetCode,
            card.CollectorNumber,
            card.Foil ? "[yellow]Yes[/]" : "",
            card.ScryfallId.Length > 8 ? card.ScryfallId[..8] + "..." : card.ScryfallId
        );
    }

    AnsiConsole.Write(table);
}

async Task ExportCsv(List<CollectionCard> collection, FileInfo outputFile)
{
    var sb = new StringBuilder();
    sb.AppendLine("Quantity,Scryfall ID,Foil,Card Name,Set Code,Collector Number");

    foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode))
    {
        var foilStr = card.Foil ? "Foil" : "Normal";
        var name = card.Name.Contains(',') ? $"\"{card.Name}\"" : card.Name;
        sb.AppendLine($"{card.Quantity},{card.ScryfallId},{foilStr},{name},{card.SetCode},{card.CollectorNumber}");
    }

    await File.WriteAllTextAsync(outputFile.FullName, sb.ToString());
}

void WriteCsvToStdout(List<CollectionCard> collection)
{
    Console.WriteLine("Quantity,Scryfall ID,Foil,Card Name,Set Code,Collector Number");

    foreach (var card in collection.OrderBy(c => c.Name).ThenBy(c => c.SetCode))
    {
        var foilStr = card.Foil ? "Foil" : "Normal";
        var name = card.Name.Contains(',') ? $"\"{card.Name}\"" : card.Name;
        Console.WriteLine($"{card.Quantity},{card.ScryfallId},{foilStr},{name},{card.SetCode},{card.CollectorNumber}");
    }
}

async Task<List<ScannedCard>> GetScannedCards(FileInfo dlensFile)
{
    var cards = new List<ScannedCard>();

    await using var connection = new SqliteConnection($"Data Source={dlensFile.FullName};Mode=ReadOnly");
    await connection.OpenAsync();

    await using var command = connection.CreateCommand();
    command.CommandText = "SELECT * FROM cards";

    await using var reader = await command.ExecuteReaderAsync();
    while (await reader.ReadAsync())
    {
        var cardId = reader.GetInt32(reader.GetOrdinal("card"));
        var quantity = reader.GetInt32(reader.GetOrdinal("quantity"));
        var foil = reader.GetInt32(reader.GetOrdinal("foil")) == 1;

        cards.Add(new ScannedCard(cardId, quantity, foil));
    }

    return cards;
}

record ScannedCard(int CardId, int Quantity, bool Foil);
record CollectionCard(int Quantity, string Name, string SetCode, string CollectorNumber, string ScryfallId, bool Foil);
