using System.Text.Json;
using Microsoft.Extensions.Options;

public interface ISessionCatalog
{
    IReadOnlyList<SessionPreset> ListSessions();
    SessionPreset? GetById(string sessionId);
}

public sealed record SessionPreset(
    string Id,
    string Name,
    string Goal,
    int EstimatedMinutes,
    double DefaultSpeed,
    IReadOnlyList<string> Scenarios);

/// <summary>
/// Loads <c>sessions.json</c> from the scenario directory (or its parent) and
/// validates every referenced scenario id against <see cref="IScenarioCatalog"/>
/// at construction — a typo fails loudly at startup.
/// </summary>
public sealed class SessionCatalog : ISessionCatalog
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
    };

    private readonly IReadOnlyList<SessionPreset> _sessions;

    public SessionCatalog(IOptions<SimulationOptions> options, IScenarioCatalog scenarios)
    {
        var directory = options.Value.ScenarioDirectory;
        var path = ResolveManifestPath(directory)
            ?? throw new InvalidOperationException(
                $"Simulation session manifest not found. Expected sessions.json in or beside " +
                $"ScenarioDirectory '{Path.GetFullPath(directory)}'.");

        _sessions = LoadAndValidate(path, scenarios);
    }

    public IReadOnlyList<SessionPreset> ListSessions() => _sessions;

    public SessionPreset? GetById(string sessionId) =>
        _sessions.FirstOrDefault(s =>
            string.Equals(s.Id, sessionId, StringComparison.OrdinalIgnoreCase));

    internal static string? ResolveManifestPath(string scenarioDirectory)
    {
        var inDir = Path.Combine(scenarioDirectory, "sessions.json");
        if (File.Exists(inDir))
            return inDir;

        var parent = Directory.GetParent(Path.GetFullPath(scenarioDirectory))?.FullName;
        if (parent is not null)
        {
            var beside = Path.Combine(parent, "sessions.json");
            if (File.Exists(beside))
                return beside;
        }

        return null;
    }

    internal static IReadOnlyList<SessionPreset> LoadAndValidate(
        string path, IScenarioCatalog scenarios)
    {
        SessionsDocument doc;
        try
        {
            var json = File.ReadAllText(path);
            doc = JsonSerializer.Deserialize<SessionsDocument>(json, JsonOptions)
                ?? throw new InvalidOperationException(
                    $"Session manifest '{path}' deserialized to null.");
        }
        catch (Exception ex) when (ex is not InvalidOperationException)
        {
            throw new InvalidOperationException(
                $"Failed to load simulation session manifest '{path}': {ex.Message}", ex);
        }

        if (doc.Sessions is null || doc.Sessions.Count == 0)
            throw new InvalidOperationException(
                $"Session manifest '{path}' contains no sessions.");

        var presets = new List<SessionPreset>(doc.Sessions.Count);
        var missing = new List<string>();

        foreach (var entry in doc.Sessions)
        {
            if (string.IsNullOrWhiteSpace(entry.Id))
                throw new InvalidOperationException(
                    $"Session manifest '{path}' has a session with an empty id.");

            if (entry.Scenarios is null || entry.Scenarios.Count == 0)
                throw new InvalidOperationException(
                    $"Session '{entry.Id}' in '{path}' lists no scenarios.");

            foreach (var scenarioId in entry.Scenarios)
            {
                if (scenarios.GetById(scenarioId) is null)
                    missing.Add($"{entry.Id} → {scenarioId}");
            }

            presets.Add(new SessionPreset(
                entry.Id,
                entry.Name ?? entry.Id,
                entry.Goal ?? string.Empty,
                entry.EstimatedMinutes,
                entry.DefaultSpeed <= 0 ? 60 : entry.DefaultSpeed,
                entry.Scenarios.AsReadOnly()));
        }

        if (missing.Count > 0)
        {
            throw new InvalidOperationException(
                "Session manifest references unknown scenario id(s): " +
                string.Join("; ", missing));
        }

        return presets;
    }

    private sealed class SessionsDocument
    {
        public List<SessionEntry>? Sessions { get; set; }
    }

    private sealed class SessionEntry
    {
        public string Id { get; set; } = null!;
        public string? Name { get; set; }
        public string? Goal { get; set; }
        public int EstimatedMinutes { get; set; }
        public double DefaultSpeed { get; set; }
        public List<string>? Scenarios { get; set; }
    }
}
