using JSMR.Application.DI;
using JSMR.Infrastructure.Data;
using JSMR.Infrastructure.DI;
using JSMR.Worker.Options;
using JSMR.Worker.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using System.CommandLine;
using System.Text;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Build a single configuration pipeline
builder.Configuration
    .SetBasePath(builder.Environment.ContentRootPath)
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables();

if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddUserSecrets(typeof(Program).Assembly, optional: true);
}

// Pull the connection string from config (appsettings or secrets or env)
string connectionString = builder.Configuration.GetConnectionString("AppDb")
    ?? throw new InvalidOperationException("Missing ConnectionStrings:AppDb");

string? seqUrl = builder.Configuration["Seq:ServerUrl"];

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    .MinimumLevel.Override("Polly", LogEventLevel.Warning)
    .WriteTo.Logger(lc => lc
        .MinimumLevel.Warning()
        .WriteTo.Console())
    .WriteTo.File(
        path: "logs/jsmr-worker-.log",
        rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Logging.ClearProviders();
builder.Services.AddSerilog();

builder.Services
    .AddApplication()
    .AddInfrastructure()
    .AddMemoryCache();

//string connectionString = builder.Configuration.GetConnectionString("AppDb")
//         ?? throw new InvalidOperationException("Missing ConnectionStrings:AppDb");

builder.Services.AddDbContextFactory<AppDbContext>(optionsBuilder =>
    optionsBuilder
        .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), mySqlOptions =>
        {
            mySqlOptions.CommandTimeout(120);
        })
        .EnableSensitiveDataLogging(false));

// Worker services
builder.Services.AddSingleton<ICheckpointStore, FileCheckpointStore>();
builder.Services.AddTransient<PagedScanRunner>();

RootCommand rootCommand = new("JSMR worker");
Command scan = new("scan", "Scan and update the database");

Option<string?> localeOption = new("--locale", "-l")
{
    Description = "Locale (Japanese/English)",
    Required = false
};

Option<int?> startOption = new("--start", "-s")
{
    Description = "Start page (default = checkpoint+1 or 1)"
};

Option<int?> endOption = new("--end", "-e")
{
    Description = "End page (optional)"
};

Option<int?> sizeOption = new("--pageSize", "-ps")
{
    Description = "Page size (default from config or 100)",
    DefaultValueFactory = _ => 100
};

Option<bool> watchOption = new("--watch", "-w")
{
    Description = "Loop forever",
    DefaultValueFactory = _ => false
};

Option<TimeSpan> everyOption = new("--every", "-e")
{
    Description = "Interval when --watch is set",
    DefaultValueFactory = _ => TimeSpan.FromMinutes(5)
};


scan.Add(localeOption);
scan.Add(startOption);
scan.Add(endOption);
scan.Add(sizeOption);
scan.Add(watchOption);
scan.Add(everyOption);

scan.SetAction(async (parseResult, cancellationToken) =>
{
    using var host = builder.Build();
    var runner = host.Services.GetRequiredService<PagedScanRunner>();

    ScanOptions options = new()
    {
        Locale = parseResult.GetValue(localeOption) ?? default!,
        StartPage = parseResult.GetValue(startOption),
        EndPage = parseResult.GetValue(endOption),
        PageSize = parseResult.GetValue(sizeOption),
        Watch = parseResult.GetValue(watchOption),
        Interval = parseResult.GetValue(everyOption)
    };

    using CancellationTokenSource cancellationTokenSource = new();

    Console.CancelKeyPress += (_, eventArgs) =>
    {
        eventArgs.Cancel = true;
        cancellationTokenSource.Cancel();
    };

    await runner.RunAsync(options, cancellationTokenSource.Token);
});

rootCommand.Add(scan);

//rootCommand.SetAction(async (parseResult, cancellationToken) => await rootCommand.InvokeAsync("scan"));

Command schemaDumpCommand = new("schema-dump", "Emit EF model as a full create script (desired.sql)");

schemaDumpCommand.SetAction(async (parseResult, cancellationToken) =>
{
    using var host = builder.Build();
    await using var scope = host.Services.CreateAsyncScope();
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

    var sql = db.Database.GenerateCreateScript();
    var outPath = Path.GetFullPath("desired.sql");
    await File.WriteAllTextAsync(outPath, sql, cancellationToken);

    Console.WriteLine($"[OK] Wrote EF model create script to: {outPath}");
});

rootCommand.Add(schemaDumpCommand);

Console.OutputEncoding = Encoding.UTF8;

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