using ClawdDotNet.Core.Tasks;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Tasks;

/// <summary>
/// Der Frontmatter-Parser ist die Brücke zwischen der menschen-/agentenlesbaren
/// Task-Datei und dem DB-Zustand. Was er falsch liest, importiert das Board falsch —
/// deshalb steht hier der Rundlauf im Mittelpunkt: Serialisieren und wieder Einlesen
/// muss dieselbe Definition ergeben.
/// </summary>
public sealed class TaskFrontmatterTests
{
    [Fact]
    public void Ein_vollstaendiges_Frontmatter_wird_gelesen()
    {
        var text = """
            ---
            id: t-8f3a2c
            title: NVDA Earnings recherchieren
            status: todo
            type: work
            priority: 4
            assignee: "@crawler"
            when:
              kind: cron
              value: "0 7 * * 1-5"
              tz: Europe/Berlin
            require_approval: true
            acceptance: |
              Aktuelle Zahlen mit Datum und Quelle.
              In SharedWorkspace/data/nvda.json abgelegt.
            blocked_by: [t-4b1e, t-9a2c]
            onlyWhenMarketOpen: false
            ---

            Recherchiere die neuesten Quartalszahlen.
            """;

        TaskFrontmatter.TryParse(text, out var task, out var error).ShouldBeTrue(error);

        task.Id.ShouldBe("t-8f3a2c");
        task.Title.ShouldBe("NVDA Earnings recherchieren");
        task.Status.ShouldBe(TaskItemStatus.Todo);
        task.Type.ShouldBe(TaskItemType.Work);
        task.Priority.ShouldBe(4);
        task.Assignee.ShouldBe("@crawler");
        task.When.ShouldNotBeNull();
        task.When!.Kind.ShouldBe(TaskWhenKind.Cron);
        task.When.Value.ShouldBe("0 7 * * 1-5");
        task.When.TimeZone.ShouldBe("Europe/Berlin");
        task.RequireApproval.ShouldBeTrue();
        task.Acceptance.ShouldContain("nvda.json");
        task.Acceptance.ShouldContain("Aktuelle Zahlen");
        task.BlockedBy.ShouldBe(["t-4b1e", "t-9a2c"]);
        task.OnlyWhenMarketOpen.ShouldBeFalse();
        task.Body.ShouldBe("Recherchiere die neuesten Quartalszahlen.");
    }

    [Fact]
    public void Serialisieren_und_wieder_Einlesen_ergibt_dieselbe_Definition()
    {
        var original = new TaskItem
        {
            Id = "t-abc123",
            Title = "Titel mit: Doppelpunkt und # Raute",
            Status = TaskItemStatus.InReview,
            Type = TaskItemType.Approval,
            Priority = 5,
            Assignee = "@human",
            When = new TaskWhen { Kind = TaskWhenKind.Every, Value = "30m", TimeZone = "UTC" },
            RequireApproval = true,
            Acceptance = "Zeile eins\nZeile zwei",
            BlockedBy = ["t-1", "t-2"],
            OnlyWhenMarketOpen = true,
            Body = "Mehrzeiliger\nRumpf mit Umlauten äöü und 🦀."
        };

        var text = TaskFrontmatter.Serialize(original);
        TaskFrontmatter.TryParse(text, out var round, out var error).ShouldBeTrue(error);

        round.Id.ShouldBe(original.Id);
        round.Title.ShouldBe(original.Title);
        round.Status.ShouldBe(original.Status);
        round.Type.ShouldBe(original.Type);
        round.Priority.ShouldBe(original.Priority);
        round.Assignee.ShouldBe(original.Assignee);
        round.When!.Kind.ShouldBe(TaskWhenKind.Every);
        round.When.Value.ShouldBe("30m");
        round.When.TimeZone.ShouldBe("UTC");
        round.RequireApproval.ShouldBeTrue();
        round.Acceptance.ShouldBe(original.Acceptance);
        round.BlockedBy.ShouldBe(["t-1", "t-2"]);
        round.OnlyWhenMarketOpen.ShouldBeTrue();
        round.Body.ShouldBe(original.Body);
    }

    [Fact]
    public void Ein_Assignee_mit_At_ueberlebt_den_Rundlauf()
    {
        // In YAML ist ein führendes '@' reserviert — ohne Quotierung ginge es verloren.
        var task = new TaskItem { Id = "t-1", Title = "x", Assignee = "@new" };

        var text = TaskFrontmatter.Serialize(task);
        TaskFrontmatter.TryParse(text, out var round, out _).ShouldBeTrue();

        round.Assignee.ShouldBe("@new");
        TaskAssignee.KindOf(round.Assignee).ShouldBe(TaskAssigneeKind.New);
    }

    [Fact]
    public void Ohne_when_ist_es_eine_einmalige_Aufgabe()
    {
        var text = """
            ---
            id: t-1
            title: Einmalig
            assignee: "@new"
            ---
            Mach das einmal.
            """;

        TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();
        task.When.ShouldBeNull();
    }

    [Fact]
    public void Fehlende_Felder_fallen_auf_sichere_Standards_zurueck()
    {
        var text = """
            ---
            id: t-1
            title: Minimal
            ---
            """;

        TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();

        task.Status.ShouldBe(TaskItemStatus.Todo);
        task.Type.ShouldBe(TaskItemType.Work);
        task.Priority.ShouldBe(3);
        task.Assignee.ShouldBe(TaskAssignee.Human, "ohne Zuordnung wartet die Aufgabe auf einen Menschen");
        task.RequireApproval.ShouldBeFalse();
        task.BlockedBy.ShouldBeEmpty();
    }

    [Fact]
    public void Eine_unbekannte_Terminart_wird_verworfen_statt_geraten()
    {
        var text = """
            ---
            id: t-1
            title: x
            when:
              kind: phantasie
              value: irgendwas
              tz: UTC
            ---
            """;

        TaskFrontmatter.TryParse(text, out var task, out _).ShouldBeTrue();
        task.When.ShouldBeNull();
    }

    [Fact]
    public void Fehlt_der_Frontmatter_Block_meldet_der_Parser_das()
    {
        TaskFrontmatter.TryParse("Nur Text, kein Frontmatter.", out _, out var error).ShouldBeFalse();
        error.ShouldNotBeNull();
    }

    [Fact]
    public void Ein_nicht_geschlossener_Block_wird_gemeldet()
    {
        var text = "---\nid: t-1\ntitle: x\n";
        TaskFrontmatter.TryParse(text, out _, out var error).ShouldBeFalse();
        error.ShouldNotBeNull();
    }

    [Fact]
    public void Windows_Zeilenenden_stoeren_nicht()
    {
        var text = "---\r\nid: t-1\r\ntitle: Mit CRLF\r\n---\r\nRumpf\r\n";
        TaskFrontmatter.TryParse(text, out var task, out var error).ShouldBeTrue(error);
        task.Title.ShouldBe("Mit CRLF");
        task.Body.ShouldBe("Rumpf");
    }
}
