using System.Text.Json;
using System.Text.Json.Serialization;
using Journal.AI;
using Journal.Core;
using Microsoft.Extensions.FileProviders;

var gatewayJsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

var repoRoot = ResolveRepoRoot();
Environment.SetEnvironmentVariable("JOURNAL_PROJECT_ROOT", repoRoot);
var webDistPath = ResolveWebDist(repoRoot);

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFragmentServices();
builder.Services.AddLlamaSharpServices();
builder.Services.AddSingleton<Entry>();
builder.Services.AddSingleton(new SidecarRootState(repoRoot));
builder.Services.AddSingleton(new WebUiState(webDistPath));

builder.Services.AddCors(options =>
{
    options.AddPolicy("GatewayCors", policy =>
    {
        policy.AllowAnyOrigin()
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.SerializerOptions.PropertyNameCaseInsensitive = true;
});

var app = builder.Build();

app.UseCors("GatewayCors");

app.MapGet("/api/health", () => Results.Ok(new
{
    ok = true,
    service = "Journal.WebGateway"
}));

app.MapGet("/api/web/status", (WebUiState webUiState) => Results.Ok(new
{
    distPath = webUiState.DistPath,
    exists = webUiState.Exists
}));

app.MapPost("/api/command", async (CommandEnvelope? command, Entry entry) =>
{
    if (command is null || string.IsNullOrWhiteSpace(command.Action))
    {
        return Results.Content(ErrorResponse("Missing action"), "application/json");
    }

    var inputJson = JsonSerializer.Serialize(command, gatewayJsonOptions);
    var responseJson = await entry.HandleCommandAsync(inputJson);
    return Results.Content(responseJson, "application/json");
});

app.MapGet("/api/sidecar/root", (SidecarRootState rootState) =>
{
    var (root, isCustom) = rootState.Get();
    return Results.Ok(new
    {
        root,
        isCustom
    });
});

app.MapPost("/api/sidecar/root", (SetSidecarRootRequest? request, SidecarRootState rootState) =>
{
    var path = request?.Path ?? "";
    if (!string.IsNullOrWhiteSpace(path) && !Directory.Exists(path))
    {
        return Results.BadRequest($"Directory '{path}' does not exist.");
    }

    rootState.Set(path);
    var (root, isCustom) = rootState.Get();
    Environment.SetEnvironmentVariable("JOURNAL_PROJECT_ROOT", root);
    return Results.Ok(new
    {
        root,
        isCustom
    });
});

if (Directory.Exists(webDistPath) && File.Exists(Path.Combine(webDistPath, "index.html")))
{
    var fileProvider = new PhysicalFileProvider(webDistPath);
    var indexPath = Path.Combine(webDistPath, "index.html");

    app.UseDefaultFiles(new DefaultFilesOptions
    {
        FileProvider = fileProvider,
        RequestPath = ""
    });

    app.UseStaticFiles(new StaticFileOptions
    {
        FileProvider = fileProvider,
        RequestPath = ""
    });

    app.MapGet("/", async context =>
    {
        context.Response.ContentType = "text/html; charset=utf-8";
        await context.Response.SendFileAsync(indexPath);
    });

    app.MapFallback(async context =>
    {
        if (context.Request.Path.StartsWithSegments("/api"))
        {
            context.Response.StatusCode = StatusCodes.Status404NotFound;
            return;
        }

        context.Response.ContentType = "text/html; charset=utf-8";
        await context.Response.SendFileAsync(indexPath);
    });
}
else
{
    app.MapGet("/", () => Results.Ok(new
    {
        name = "Journal.WebGateway",
        status = "ok",
        uiAvailable = false,
        message = "No built web UI found. Build Journal.App with ./scripts/publish-app.ps1 -Target web.",
        expectedDist = webDistPath
    }));
}

app.Run();

string ResolveRepoRoot()
{
    var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_PROJECT_ROOT");
    if (!string.IsNullOrWhiteSpace(fromEnv) && Directory.Exists(fromEnv))
    {
        return Path.GetFullPath(fromEnv);
    }

    foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory })
    {
        var resolved = FindRepoRoot(start);
        if (resolved is not null)
        {
            return resolved;
        }
    }

    return Path.GetFullPath(Directory.GetCurrentDirectory());
}

string? FindRepoRoot(string start)
{
    var cursor = Path.GetFullPath(start);

    while (!string.IsNullOrWhiteSpace(cursor))
    {
        if (File.Exists(Path.Combine(cursor, "Journal.slnx")) ||
            Directory.Exists(Path.Combine(cursor, "Journal.Sidecar")) ||
            Directory.Exists(Path.Combine(cursor, "Journal.Core")))
        {
            return cursor;
        }

        var parent = Directory.GetParent(cursor);
        if (parent is null || string.Equals(parent.FullName, cursor, StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        cursor = parent.FullName;
    }

    return null;
}

string ResolveWebDist(string repoRootPath)
{
    var fromEnv = Environment.GetEnvironmentVariable("JOURNAL_WEB_DIST");
    if (!string.IsNullOrWhiteSpace(fromEnv))
    {
        return Path.GetFullPath(fromEnv);
    }

    var packagedWwwRoot = Path.Combine(AppContext.BaseDirectory, "wwwroot");
    if (Directory.Exists(packagedWwwRoot))
    {
        return packagedWwwRoot;
    }

    return Path.Combine(repoRootPath, "Journal.App", "build");
}

string ErrorResponse(string message)
    => JsonSerializer.Serialize(new { ok = false, error = message }, gatewayJsonOptions);

sealed class WebUiState(string distPath)
{
    public string DistPath { get; } = distPath;

    public bool Exists => Directory.Exists(DistPath) && File.Exists(Path.Combine(DistPath, "index.html"));
}

sealed class SidecarRootState(string autoRoot)
{
    private readonly object _sync = new();
    private readonly string _autoRoot = autoRoot;
    private string _currentRoot = autoRoot;
    private bool _isCustom;

    public (string Root, bool IsCustom) Get()
    {
        lock (_sync)
        {
            return (_currentRoot, _isCustom);
        }
    }

    public void Set(string? path)
    {
        lock (_sync)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                _currentRoot = _autoRoot;
                _isCustom = false;
                return;
            }

            _currentRoot = Path.GetFullPath(path.Trim());
            _isCustom = true;
        }
    }
}

sealed class SetSidecarRootRequest
{
    public string? Path { get; set; }
}

sealed class CommandEnvelope
{
    public string Action { get; set; } = "";
    public string? CorrelationId { get; set; }
    public string? Id { get; set; }
    public string? Type { get; set; }
    public string? Tag { get; set; }
    public JsonElement? Payload { get; set; }
}
