using System;
using System.CommandLine;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cli.Configuration;

namespace StellaOps.Cli.Commands;

internal static class CommandFactory
{
    public static RootCommand Create(IServiceProvider services, StellaOpsCliOptions options, CancellationToken cancellationToken)
    {
        var verboseOption = new Option<bool>("--verbose", new[] { "-v" })
        {
            Description = "Enable verbose logging output."
        };

        var root = new RootCommand("StellaOps command-line interface")
        {
            TreatUnmatchedTokensAsErrors = true
        };
        root.Add(verboseOption);

        root.Add(BuildScannerCommand(services, verboseOption, cancellationToken));
        root.Add(BuildScanCommand(services, options, verboseOption, cancellationToken));
        root.Add(BuildDatabaseCommand(services, verboseOption, cancellationToken));
        root.Add(BuildAuthCommand(services, options, verboseOption, cancellationToken));
        root.Add(BuildConfigCommand(options));

        return root;
    }

    private static Command BuildScannerCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
    {
        var scanner = new Command("scanner", "Manage scanner artifacts and lifecycle.");

        var download = new Command("download", "Download the latest scanner bundle.");
        var channelOption = new Option<string>("--channel", new[] { "-c" })
        {
            Description = "Scanner channel (stable, beta, nightly)."
        };

        var outputOption = new Option<string?>("--output")
        {
            Description = "Optional output path for the downloaded bundle."
        };

        var overwriteOption = new Option<bool>("--overwrite")
        {
            Description = "Overwrite existing bundle if present."
        };

        var noInstallOption = new Option<bool>("--no-install")
        {
            Description = "Skip installing the scanner container after download."
        };

        download.Add(channelOption);
        download.Add(outputOption);
        download.Add(overwriteOption);
        download.Add(noInstallOption);

        download.SetAction((parseResult, _) =>
        {
            var channel = parseResult.GetValue(channelOption) ?? "stable";
            var output = parseResult.GetValue(outputOption);
            var overwrite = parseResult.GetValue(overwriteOption);
            var install = !parseResult.GetValue(noInstallOption);
            var verbose = parseResult.GetValue(verboseOption);

            return CommandHandlers.HandleScannerDownloadAsync(services, channel, output, overwrite, install, verbose, cancellationToken);
        });

        scanner.Add(download);
        return scanner;
    }

    private static Command BuildScanCommand(IServiceProvider services, StellaOpsCliOptions options, Option<bool> verboseOption, CancellationToken cancellationToken)
    {
        var scan = new Command("scan", "Execute scanners and manage scan outputs.");

        var run = new Command("run", "Execute a scanner bundle with the configured runner.");
        var runnerOption = new Option<string>("--runner")
        {
            Description = "Execution runtime (dotnet, self, docker)."
        };
        var entryOption = new Option<string>("--entry")
        {
            Description = "Path to the scanner entrypoint or Docker image.",
            Required = true
        };
        var targetOption = new Option<string>("--target")
        {
            Description = "Directory to scan.",
            Required = true
        };

        var argsArgument = new Argument<string[]>("scanner-args")
        {
            Arity = ArgumentArity.ZeroOrMore
        };

        run.Add(runnerOption);
        run.Add(entryOption);
        run.Add(targetOption);
        run.Add(argsArgument);

        run.SetAction((parseResult, _) =>
        {
            var runner = parseResult.GetValue(runnerOption) ?? options.DefaultRunner;
            var entry = parseResult.GetValue(entryOption) ?? string.Empty;
            var target = parseResult.GetValue(targetOption) ?? string.Empty;
            var forwardedArgs = parseResult.GetValue(argsArgument) ?? Array.Empty<string>();
            var verbose = parseResult.GetValue(verboseOption);

            return CommandHandlers.HandleScannerRunAsync(services, runner, entry, target, forwardedArgs, verbose, cancellationToken);
        });

        var upload = new Command("upload", "Upload completed scan results to the backend.");
        var fileOption = new Option<string>("--file")
        {
            Description = "Path to the scan result artifact.",
            Required = true
        };
        upload.Add(fileOption);
        upload.SetAction((parseResult, _) =>
        {
            var file = parseResult.GetValue(fileOption) ?? string.Empty;
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleScanUploadAsync(services, file, verbose, cancellationToken);
        });

        scan.Add(run);
        scan.Add(upload);
        return scan;
    }

    private static Command BuildDatabaseCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
    {
        var db = new Command("db", "Trigger Feedser database operations via backend jobs.");

        var fetch = new Command("fetch", "Trigger connector fetch/parse/map stages.");
        var sourceOption = new Option<string>("--source")
        {
            Description = "Connector source identifier (e.g. redhat, osv, vmware).",
            Required = true
        };
        var stageOption = new Option<string>("--stage")
        {
            Description = "Stage to trigger: fetch, parse, or map."
        };
        var modeOption = new Option<string?>("--mode")
        {
            Description = "Optional connector-specific mode (init, resume, cursor)."
        };

        fetch.Add(sourceOption);
        fetch.Add(stageOption);
        fetch.Add(modeOption);
        fetch.SetAction((parseResult, _) =>
        {
            var source = parseResult.GetValue(sourceOption) ?? string.Empty;
            var stage = parseResult.GetValue(stageOption) ?? "fetch";
            var mode = parseResult.GetValue(modeOption);
            var verbose = parseResult.GetValue(verboseOption);

            return CommandHandlers.HandleConnectorJobAsync(services, source, stage, mode, verbose, cancellationToken);
        });

        var merge = new Command("merge", "Run canonical merge reconciliation.");
        merge.SetAction((parseResult, _) =>
        {
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleMergeJobAsync(services, verbose, cancellationToken);
        });

        var export = new Command("export", "Run Feedser export jobs.");
        var formatOption = new Option<string>("--format")
        {
            Description = "Export format: json or trivy-db."
        };
        var deltaOption = new Option<bool>("--delta")
        {
            Description = "Request a delta export when supported."
        };
        var publishFullOption = new Option<bool?>("--publish-full")
        {
            Description = "Override whether full exports push to ORAS (true/false)."
        };
        var publishDeltaOption = new Option<bool?>("--publish-delta")
        {
            Description = "Override whether delta exports push to ORAS (true/false)."
        };
        var includeFullOption = new Option<bool?>("--bundle-full")
        {
            Description = "Override whether offline bundles include full exports (true/false)."
        };
        var includeDeltaOption = new Option<bool?>("--bundle-delta")
        {
            Description = "Override whether offline bundles include delta exports (true/false)."
        };

        export.Add(formatOption);
        export.Add(deltaOption);
        export.Add(publishFullOption);
        export.Add(publishDeltaOption);
        export.Add(includeFullOption);
        export.Add(includeDeltaOption);
        export.SetAction((parseResult, _) =>
        {
            var format = parseResult.GetValue(formatOption) ?? "json";
            var delta = parseResult.GetValue(deltaOption);
            var publishFull = parseResult.GetValue(publishFullOption);
            var publishDelta = parseResult.GetValue(publishDeltaOption);
            var includeFull = parseResult.GetValue(includeFullOption);
            var includeDelta = parseResult.GetValue(includeDeltaOption);
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleExportJobAsync(services, format, delta, publishFull, publishDelta, includeFull, includeDelta, verbose, cancellationToken);
        });

        db.Add(fetch);
        db.Add(merge);
        db.Add(export);
        return db;
    }

    private static Command BuildAuthCommand(IServiceProvider services, StellaOpsCliOptions options, Option<bool> verboseOption, CancellationToken cancellationToken)
    {
        var auth = new Command("auth", "Manage authentication with StellaOps Authority.");

        var login = new Command("login", "Acquire and cache access tokens using the configured credentials.");
        var forceOption = new Option<bool>("--force")
        {
            Description = "Ignore existing cached tokens and force re-authentication."
        };
        login.Add(forceOption);
        login.SetAction((parseResult, _) =>
        {
            var verbose = parseResult.GetValue(verboseOption);
            var force = parseResult.GetValue(forceOption);
            return CommandHandlers.HandleAuthLoginAsync(services, options, verbose, force, cancellationToken);
        });

        var logout = new Command("logout", "Remove cached tokens for the current credentials.");
        logout.SetAction((parseResult, _) =>
        {
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleAuthLogoutAsync(services, options, verbose, cancellationToken);
        });

        var status = new Command("status", "Display cached token status.");
        status.SetAction((parseResult, _) =>
        {
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleAuthStatusAsync(services, options, verbose, cancellationToken);
        });

        var whoami = new Command("whoami", "Display cached token claims (subject, scopes, expiry).");
        whoami.SetAction((parseResult, _) =>
        {
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleAuthWhoAmIAsync(services, options, verbose, cancellationToken);
        });

        var revoke = new Command("revoke", "Manage revocation exports.");
        var export = new Command("export", "Export the revocation bundle and signature to disk.");
        var outputOption = new Option<string?>("--output")
        {
            Description = "Directory to write exported revocation files (defaults to current directory)."
        };
        export.Add(outputOption);
        export.SetAction((parseResult, _) =>
        {
            var output = parseResult.GetValue(outputOption);
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleAuthRevokeExportAsync(services, options, output, verbose, cancellationToken);
        });
        revoke.Add(export);
        var verify = new Command("verify", "Verify a revocation bundle against a detached JWS signature.");
        var bundleOption = new Option<string>("--bundle")
        {
            Description = "Path to the revocation-bundle.json file."
        };
        var signatureOption = new Option<string>("--signature")
        {
            Description = "Path to the revocation-bundle.json.jws file."
        };
        var keyOption = new Option<string>("--key")
        {
            Description = "Path to the PEM-encoded public/private key used for verification."
        };
        verify.Add(bundleOption);
        verify.Add(signatureOption);
        verify.Add(keyOption);
        verify.SetAction((parseResult, _) =>
        {
            var bundlePath = parseResult.GetValue(bundleOption) ?? string.Empty;
            var signaturePath = parseResult.GetValue(signatureOption) ?? string.Empty;
            var keyPath = parseResult.GetValue(keyOption) ?? string.Empty;
            var verbose = parseResult.GetValue(verboseOption);
            return CommandHandlers.HandleAuthRevokeVerifyAsync(bundlePath, signaturePath, keyPath, verbose, cancellationToken);
        });
        revoke.Add(verify);

        auth.Add(login);
        auth.Add(logout);
        auth.Add(status);
        auth.Add(whoami);
        auth.Add(revoke);
        return auth;
    }

    private static Command BuildConfigCommand(StellaOpsCliOptions options)
    {
        var config = new Command("config", "Inspect CLI configuration state.");
        var show = new Command("show", "Display resolved configuration values.");

        show.SetAction((_, _) =>
        {
            var authority = options.Authority ?? new StellaOpsCliAuthorityOptions();
            var lines = new[]
            {
                $"Backend URL: {MaskIfEmpty(options.BackendUrl)}",
                $"API Key: {DescribeSecret(options.ApiKey)}",
                $"Scanner Cache: {options.ScannerCacheDirectory}",
                $"Results Directory: {options.ResultsDirectory}",
                $"Default Runner: {options.DefaultRunner}",
                $"Authority URL: {MaskIfEmpty(authority.Url)}",
                $"Authority Client ID: {MaskIfEmpty(authority.ClientId)}",
                $"Authority Client Secret: {DescribeSecret(authority.ClientSecret ?? string.Empty)}",
                $"Authority Username: {MaskIfEmpty(authority.Username)}",
                $"Authority Password: {DescribeSecret(authority.Password ?? string.Empty)}",
                $"Authority Scope: {MaskIfEmpty(authority.Scope)}",
                $"Authority Token Cache: {MaskIfEmpty(authority.TokenCacheDirectory ?? string.Empty)}"
            };

            foreach (var line in lines)
            {
                Console.WriteLine(line);
            }

            return Task.CompletedTask;
        });

        config.Add(show);
        return config;
    }

    private static string MaskIfEmpty(string value)
        => string.IsNullOrWhiteSpace(value) ? "<not configured>" : value;

    private static string DescribeSecret(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return "<not configured>";
        }

        return value.Length switch
        {
            <= 4 => "****",
            _ => $"{value[..2]}***{value[^2..]}"
        };
    }
}
