using Flo.Installer.Provisioning;

namespace Flo.Installer.Commands;

internal static class ProvisionCommand
{
    private const string DefaultSuperuser = "postgres";
    private const string DefaultRole      = "flo_app";
    private const string DefaultDatabase  = "flo";

    public static async Task<int> ExecuteAsync(string installPath, CancellationToken ct = default)
    {
        var paths = new Paths(installPath);
        Directory.CreateDirectory(paths.Config);
        Directory.CreateDirectory(paths.Logs);

        if (!File.Exists(paths.PostgresBin("initdb")))
            throw new InvalidOperationException($"Postgres binaries not found at {paths.Postgres}. Run `install` first.");
        if (!File.Exists(paths.FerretDbExe()))
            throw new InvalidOperationException($"FerretDB binary not found at {paths.FerretDbExe()}. Run `install` first.");

        var secretsStore = new SecretsStore(paths.SecretsFile);

        FloConfig config;
        Dictionary<string, string> secrets;

        if (File.Exists(paths.ConfigFile) && secretsStore.Exists())
        {
            Console.WriteLine("Existing flo.json + secrets.json found — reusing ports and passwords.");
            config = FloConfig.Load(paths.ConfigFile);
            secrets = new Dictionary<string, string>(secretsStore.Load());
        }
        else
        {
            int pgPort = PortScanner.FindFreeLoopbackPort();
            int fdPort = PortScanner.FindFreeLoopbackPort();
            while (fdPort == pgPort) fdPort = PortScanner.FindFreeLoopbackPort();

            config = new FloConfig(
                PostgresPort:      pgPort,
                FerretDbPort:      fdPort,
                PostgresSuperuser: DefaultSuperuser,
                ApplicationDbRole: DefaultRole,
                ApplicationDbName: DefaultDatabase);

            secrets = new Dictionary<string, string>
            {
                [$"pg.{config.PostgresSuperuser}"] = PasswordGenerator.Generate(),
                [$"pg.{config.ApplicationDbRole}"] = PasswordGenerator.Generate(),
            };

            config.Save(paths.ConfigFile);
            secretsStore.Save(secrets);
            Console.WriteLine($"Allocated Postgres port {pgPort}, FerretDB port {fdPort}.");
        }

        var postgres = new PostgresProvisioner(paths);

        if (!postgres.IsInitialized())
        {
            Console.WriteLine("Running initdb...");
            postgres.Initialize(config.PostgresSuperuser, secrets[$"pg.{config.PostgresSuperuser}"]);
            Console.WriteLine("initdb complete.");
        }
        else
        {
            Console.WriteLine("Postgres data dir already initialized; skipping initdb.");
        }

        postgres.ConfigurePort(config.PostgresPort);

        Console.WriteLine("Starting Postgres temporarily to create role + database...");
        await using (var pg = postgres.Start(config.PostgresPort))
        {
            await HealthCheck.WaitForPortAsync(
                "127.0.0.1", config.PostgresPort, TimeSpan.FromSeconds(30), "Postgres", ct);

            postgres.EnsureRoleAndDatabase(
                config.PostgresPort,
                config.PostgresSuperuser, secrets[$"pg.{config.PostgresSuperuser}"],
                config.ApplicationDbRole, secrets[$"pg.{config.ApplicationDbRole}"],
                config.ApplicationDbName);
            Console.WriteLine($"Ensured role '{config.ApplicationDbRole}' and database '{config.ApplicationDbName}'.");

            postgres.StopGracefully();
        }

        Console.WriteLine("Provisioning complete.");
        return 0;
    }
}
