using ClaudeDo.Data.Models;

namespace ClaudeDo.Data.Tests;

public class ModelPresetsTests
{
    [Fact]
    public void Defaults_cover_every_known_alias()
    {
        Assert.Equal(
            ModelRegistry.Aliases.OrderBy(a => a),
            ModelPresets.Defaults.Select(p => p.Model).OrderBy(m => m));
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("not json at all")]
    public void Parse_falls_back_to_defaults_when_unusable(string? json)
    {
        Assert.Equal(ModelPresets.Defaults, ModelPresets.Parse(json));
    }

    [Fact]
    public void Serialize_roundtrips()
    {
        var edited = new[] { new ModelPreset("opus", "max", 55) };
        var parsed = ModelPresets.Parse(ModelPresets.Serialize(edited));

        Assert.Equal("max", ModelPresets.For(parsed, "opus").Effort);
        Assert.Equal(55, ModelPresets.For(parsed, "opus").MaxTurns);
    }

    [Fact]
    public void Serialize_fills_missing_aliases_from_defaults()
    {
        var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("opus", "max", 55) }));

        Assert.Equal(ModelRegistry.Aliases.Count, parsed.Count);
        Assert.Equal(ModelPresets.For(ModelPresets.Defaults, "haiku"), ModelPresets.For(parsed, "haiku"));
    }

    [Theory]
    [InlineData("nonsense", 30)]   // unknown effort → the default row's effort
    [InlineData("high", 0)]        // turns out of range → the default row's turns
    [InlineData("high", 999)]
    public void Serialize_rejects_out_of_range_values(string effort, int turns)
    {
        var fallback = ModelPresets.For(ModelPresets.Defaults, "sonnet");
        var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("sonnet", effort, turns) }));
        var row = ModelPresets.For(parsed, "sonnet");

        if (effort == "nonsense") Assert.Equal(fallback.Effort, row.Effort);
        if (turns is 0 or 999) Assert.Equal(fallback.MaxTurns, row.MaxTurns);
    }

    [Fact]
    public void For_unknown_model_yields_a_usable_row()
    {
        var row = ModelPresets.For(ModelPresets.Defaults, "some-future-model");

        Assert.Equal("some-future-model", row.Model);
        Assert.Contains(row.Effort, EffortRegistry.Levels);
        Assert.True(row.MaxTurns > 0);
    }

    [Fact]
    public void For_unknown_model_uses_the_supplied_fallback_max_turns_instead_of_a_hardcoded_number()
    {
        var row = ModelPresets.For(ModelPresets.Defaults, "some-future-model", fallbackMaxTurns: 100);

        Assert.Equal(100, row.MaxTurns);
    }

    [Fact]
    public void For_a_full_model_id_resolves_to_its_alias_preset_row_not_the_unknown_fallback()
    {
        var sonnetPreset = ModelPresets.For(ModelPresets.Defaults, "sonnet");

        var row = ModelPresets.For(ModelPresets.Defaults, "claude-sonnet-4-6");

        Assert.Equal(sonnetPreset.Effort, row.Effort);
        Assert.Equal(sonnetPreset.MaxTurns, row.MaxTurns);
    }

    [Fact]
    public void For_a_full_model_id_honors_a_customized_preset_row_for_its_alias()
    {
        var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("sonnet", "xhigh", 77) }));

        var row = ModelPresets.For(parsed, "claude-sonnet-4-6");

        Assert.Equal("xhigh", row.Effort);
        Assert.Equal(77, row.MaxTurns);
    }

    [Fact]
    public void For_unknown_model_never_throws_and_still_starts_a_run()
    {
        var exception = Record.Exception(() => ModelPresets.For(ModelPresets.Defaults, "totally-unrecognized-model"));

        Assert.Null(exception);
    }

    [Fact]
    public void For_null_model_uses_the_default_alias()
    {
        Assert.Equal(
            ModelPresets.For(ModelPresets.Defaults, ModelRegistry.DefaultAlias),
            ModelPresets.For(ModelPresets.Defaults, null));
    }

    [Theory]
    [InlineData("low", "low")]
    [InlineData("XHIGH", "xhigh")]
    [InlineData(" max ", "max")]
    public void NormalizeLevel_canonicalizes(string input, string expected)
    {
        Assert.Equal(expected, EffortRegistry.NormalizeLevel(input));
    }

    [Fact]
    public void NormalizeLevel_blank_means_unset_and_unknown_throws()
    {
        Assert.Null(EffortRegistry.NormalizeLevel(null));
        Assert.Throws<ArgumentException>(() => EffortRegistry.NormalizeLevel("gigantic"));
    }
}
