using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using {{ namespace }}.Auth;
using {{ namespace }}.Core;
using {{ namespace }}.Data;
using {{ namespace }}.Http;
using {{ namespace }}.Services;
using {{ namespace }}.Tools;

namespace {{ namespace }}.Cli;

/// <summary>
/// Terminal Client (`search`/`get`/`call`/`populate-embeddings` — this
/// story; `test-connection`/`setup` — Story C7 — run as direct CLI
/// subcommands, calling the same tool implementations
/// `Tools/McpTools.cs`'s MCP-protocol path uses, just in-process instead
/// of over stdio/HTTP) and Harness Server (`start`/`http` boot the
/// persistent MCP server loop) roles. mcpify always emits both stdio and
/// HTTP capability; transport *selection* is runtime config (REQ-2.2's
/// cascade), not a generation-time decision — mirrors `targets::python`'s
/// `_run_harness_server()`.
/// </summary>
public static class Roles
{
    public static async Task<int> RunStdioHarnessAsync(string[] args, CancellationToken cancellationToken)
    {
        var builder = Host.CreateApplicationBuilder(args);
        builder.Configuration.AddMcpifyConfiguration("{{ project_name }}", args);
        // Set explicitly (not left to the config cascade's own default):
        // AuthManager.ApplyAuthHeadersAsync decides whether to source
        // credentials from the config cascade or from per-request headers
        // based on options.Transport, so it must always agree with which
        // command actually ran, not whatever value the cascade would
        // otherwise resolve to.
        builder.Configuration["Transport"] = nameof(Transport.Stdio);
        builder.Services.AddMcpifyServices();
        builder.Services.AddMcpServer().WithStdioServerTransport();

        using var host = builder.Build();
        await host.RunAsync(cancellationToken);
        return 0;
    }

    public static async Task<int> RunHttpHarnessAsync(
        string[] args,
        string? hostOverride,
        int? portOverride,
        string? corsAllowOverride,
        CancellationToken cancellationToken)
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Configuration.AddMcpifyConfiguration("{{ project_name }}", args);
        // See RunStdioHarnessAsync's comment above.
        builder.Configuration["Transport"] = nameof(Transport.Http);
        if (hostOverride is not null)
        {
            builder.Configuration["Host"] = hostOverride;
        }
        if (portOverride is not null)
        {
            builder.Configuration["Port"] = portOverride.Value.ToString();
        }
        if (corsAllowOverride is not null)
        {
            builder.Configuration["CorsAllow"] = corsAllowOverride;
        }
        builder.Services.AddMcpifyServices();
        // Core/McpServer.cs's AddMcpServer() is deliberately
        // transport-agnostic (`.WithToolsFromAssembly()` only) — each
        // harness role adds its own transport, mirroring
        // RunStdioHarnessAsync's `.WithStdioServerTransport()` above.
        builder.Services.AddMcpServer().WithHttpTransport();

        var app = HttpServer.Configure(builder);
        var options = app.Services.GetRequiredService<IOptions<McpifyOptions>>().Value;
        app.Urls.Add($"http://{options.Host}:{options.Port}");

        {%- if "oauth1" in auth_method_keys %}
        if (options.AuthMethod == AuthMethod.OAuth1)
        {
            throw new AuthException(
                "OAuth1 is not supported over HTTP transport: its signature must be recomputed per " +
                "request against the destination URL using the server's consumer key/secret, which a " +
                "simple incoming-header relay cannot provide. Use stdio transport for OAuth1-authenticated " +
                "APIs, or switch auth_method.");
        }
        {%- endif %}

        await app.RunAsync(cancellationToken);
        return 0;
    }

    public static void PrintVersion()
    {
        var version = typeof(Roles).Assembly.GetName().Version;
        Console.WriteLine(version?.ToString() ?? "0.0.0");
    }

    /// <summary>Prints the resolved configuration with secrets redacted (REQ-2.3.1's redaction principle applied to this output too).</summary>
    public static Task PrintConfigAsync(string[] args, CancellationToken cancellationToken)
    {
        var builder = Host.CreateApplicationBuilder(args);
        builder.Configuration.AddMcpifyConfiguration("{{ project_name }}", args);
        builder.Services.AddOptions<McpifyOptions>().BindConfiguration(string.Empty);

        using var host = builder.Build();
        var options = host.Services.GetRequiredService<IOptions<McpifyOptions>>().Value;

        Console.WriteLine($"Url: {options.Url}");
        Console.WriteLine($"AuthMethod: {options.AuthMethod}");
        Console.WriteLine("Credentials: ***REDACTED***");
        Console.WriteLine($"LogLevel: {options.LogLevel}");
        Console.WriteLine($"Transport: {options.Transport}");
        Console.WriteLine($"Host: {options.Host}");
        Console.WriteLine($"Port: {options.Port}");
        return Task.CompletedTask;
    }

    /// <summary>Lists the API spec versions this project has a store for (v8 multi-version support), showing which is default/active.</summary>
    public static Task PrintVersionsAsync(string[] args, CancellationToken cancellationToken)
    {
        using var host = BuildServicesHost(args);
        var options = host.Services.GetRequiredService<IOptions<McpifyOptions>>().Value;

        foreach (var row in VersionsCommand.KnownVersions)
        {
            var markers = new List<string>();
            if (row.IsDefault)
            {
                markers.Add("default");
            }
            if (row.Label == options.ApiVersion)
            {
                markers.Add("active");
            }
            Console.WriteLine(markers.Count > 0 ? $"{row.Label} ({string.Join(", ", markers)})" : row.Label);
        }
        return Task.CompletedTask;
    }

    public static async Task<int> RunSearchAsync(string[] args, string query, int limit)
    {
        using var host = BuildServicesHost(args);
        var result = await McpTools.Search(
            host.Services.GetRequiredService<SqliteVecStore>(),
            host.Services.GetRequiredService<EmbeddingService>(),
            query,
            limit);
        Console.WriteLine(result);
        return 0;
    }

    public static int RunGet(string[] args, string operationId)
    {
        using var host = BuildServicesHost(args);
        var result = McpTools.Get(host.Services.GetRequiredService<SqliteVecStore>(), operationId);
        Console.WriteLine(result);
        return 0;
    }

    public static async Task<int> RunCallAsync(string[] args, string operationId, string callArgs)
    {
        using var host = BuildServicesHost(args);
        var result = await McpTools.Call(
            host.Services.GetRequiredService<SqliteVecStore>(),
            host.Services.GetRequiredService<ApiClient>(),
            host.Services.GetRequiredService<IOptions<McpifyOptions>>(),
            host.Services.GetRequiredService<IRequestCredentialProvider>(),
            operationId,
            callArgs);
        Console.WriteLine(result);
        return 0;
    }

    public static async Task<int> RunPopulateEmbeddingsAsync(
        string[] args, bool all, string? apiVersion, CancellationToken cancellationToken)
    {
        using var host = BuildServicesHost(args);
        var embeddingService = host.Services.GetRequiredService<EmbeddingService>();

        foreach (var dbPath in PopulateEmbeddingsService.TargetDbPaths(all, apiVersion))
        {
            var count = await new PopulateEmbeddingsService(new SqliteVecStore(dbPath), embeddingService)
                .PopulateAsync(cancellationToken);
            Console.WriteLine($"populated {count} embedding(s) in '{dbPath}'");
        }
        return 0;
    }

    public static async Task<int> RunSetupAsync(string[] args)
    {
        using var host = BuildServicesHost(args);
        await SetupWizard.RunAsync(host.Services.GetRequiredService<ICredentialStore>());
        return 0;
    }

    public static async Task<int> RunTestConnectionAsync(string[] args)
    {
        using var host = BuildServicesHost(args);
        var options = host.Services.GetRequiredService<IOptions<McpifyOptions>>().Value;

        try
        {
            using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
            using var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, options.Url));
            Console.WriteLine($"reachable (status {(int)response.StatusCode})");
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"unreachable: {ex.Message}");
            return 1;
        }
    }

    private static IHost BuildServicesHost(string[] args)
    {
        var builder = Host.CreateApplicationBuilder(args);
        builder.Configuration.AddMcpifyConfiguration("{{ project_name }}", args);
        builder.Services.AddMcpifyServices();
        return builder.Build();
    }
}
