// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: LicenseRef-MutuaL-1.2

package mcp

import (
	"context"
	"testing"

	"git.secluded.site/cooked-mcp/internal/cooked"
)

// recipes.SAVE.7 recipes.SAVE.7-2 recipes.SAVE.9 recipes.SAVE.16 recipes.SAFETY.1 tools.SAVE_RECIPE_TOOL.1 tools.SAVE_RECIPE_TOOL.5
func TestCallToolACIDRecipesSave7And9And16SavesReviewedDraft(t *testing.T) {
	draftID := "draft/with space"
	backend := &fakeBackend{draftSaveRecipeID: testRecipeID}
	server := NewServer(backend, "test")

	_, output, err := server.callSaveRecipeTool(
		context.Background(),
		nil,
		SaveRecipeArguments{
			Source:   " draft ",
			DraftID:  " " + draftID + " ",
			Markdown: "  # Pasta\n\n1. Boil pasta.\n",
			Portions: 2,
		},
	)
	if err != nil {
		t.Fatalf("callSaveRecipeTool() error = %v", err)
	}

	if backend.draftSaveCalls != 1 {
		t.Fatalf("draft save calls = %d, want 1", backend.draftSaveCalls)
	}
	if backend.draftSaveID != draftID {
		t.Fatalf("backend draft ID = %q, want draft ID", backend.draftSaveID)
	}
	if backend.draftSaveMarkdown != "  # Pasta\n\n1. Boil pasta.\n" {
		t.Fatalf("backend draft markdown = %q, want raw markdown preserved", backend.draftSaveMarkdown)
	}
	if backend.draftSavePortions != 2 {
		t.Fatalf("backend draft portions = %g, want 2", backend.draftSavePortions)
	}
	if output.RecipeID != testRecipeID || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
		t.Fatalf("draft save output = %#v, want recipe_id only", output)
	}
	if backend.importRecipeURLCalls != 0 || backend.metadataCalls != 0 || backend.contentCalls != 0 {
		t.Fatalf(
			"unexpected import/read calls = import %d metadata %d content %d, want none",
			backend.importRecipeURLCalls,
			backend.metadataCalls,
			backend.contentCalls,
		)
	}
	if backend.saveCalls != 0 || backend.updateCalls != 0 {
		t.Fatalf("prepared/update calls = %d/%d, want none", backend.saveCalls, backend.updateCalls)
	}
}

// recipes.SAVE.7-5 recipes.SAVE.9-1 recipes.SAVE.16 recipes.SAFETY.1 recipes.SAFETY.2
func TestCallToolACIDRecipesSave7_5And9_1OverwritesExistingRecipeFromDraft(t *testing.T) {
	backend := &fakeBackend{}
	server := NewServer(backend, "test")

	_, output, err := server.callSaveRecipeTool(
		context.Background(),
		nil,
		SaveRecipeArguments{
			Source:   " draft ",
			DraftID:  " " + testDraftID + " ",
			RecipeID: " " + testRecipeID + " ",
			Markdown: "  # Pasta\n\n1. Boil pasta.\n",
			Portions: 2,
		},
	)
	if err != nil {
		t.Fatalf("callSaveRecipeTool() error = %v", err)
	}

	if backend.updateCalls != 1 {
		t.Fatalf("update calls = %d, want 1", backend.updateCalls)
	}
	if backend.updateRecipeID != testRecipeID {
		t.Fatalf("backend update recipe ID = %q, want test recipe ID", backend.updateRecipeID)
	}
	if backend.updateMarkdown != "  # Pasta\n\n1. Boil pasta.\n" {
		t.Fatalf("backend update markdown = %q, want raw markdown preserved", backend.updateMarkdown)
	}
	if backend.updatePortions != 2 {
		t.Fatalf("backend update portions = %g, want 2", backend.updatePortions)
	}
	if output.RecipeID != testRecipeID {
		t.Fatalf("save output recipe ID = %q, want test recipe ID", output.RecipeID)
	}
	if backend.draftSaveCalls != 0 || backend.saveCalls != 0 {
		t.Fatalf("draft save/prepared calls = %d/%d, want none", backend.draftSaveCalls, backend.saveCalls)
	}
}

// recipes.SAVE.7 recipes.SAVE.7-3 recipes.SAVE.9 recipes.SAVE.16 tools.SAVE_RECIPE_TOOL.5
func TestCallToolACIDRecipesSave7_3SavesCurrentDraftContentWhenContentOmitted(t *testing.T) {
	backend := &fakeBackend{
		draftSaveRecipeID: testRecipeID,
		recipeContent: cooked.RecipeContent{
			Content:  "# Pasta\n\n1. Boil pasta.",
			Portions: 1.5,
		},
	}
	server := NewServer(backend, "test")

	_, output, err := server.callSaveRecipeTool(
		context.Background(),
		nil,
		SaveRecipeArguments{Source: "draft", DraftID: " " + testDraftID + " "},
	)
	if err != nil {
		t.Fatalf("callSaveRecipeTool() error = %v", err)
	}

	if backend.contentCalls != 1 || backend.contentRecipeID != testDraftID {
		t.Fatalf("content reads = %d for %q, want 1 for test draft ID", backend.contentCalls, backend.contentRecipeID)
	}
	if backend.draftSaveCalls != 1 {
		t.Fatalf("draft save calls = %d, want 1", backend.draftSaveCalls)
	}
	if backend.draftSaveMarkdown != "# Pasta\n\n1. Boil pasta." {
		t.Fatalf("backend draft markdown = %q, want current draft content", backend.draftSaveMarkdown)
	}
	if backend.draftSavePortions != 1.5 {
		t.Fatalf("backend draft portions = %g, want current draft portions 1.5", backend.draftSavePortions)
	}
	if output.RecipeID != testRecipeID || output.DraftID != "" || output.Markdown != "" || output.Portions != 0 {
		t.Fatalf("draft save output = %#v, want recipe_id only", output)
	}
	if backend.metadataCalls != 0 || backend.importRecipeURLCalls != 0 {
		t.Fatalf(
			"unexpected metadata/import calls = %d/%d, want none",
			backend.metadataCalls,
			backend.importRecipeURLCalls,
		)
	}
}

// recipes.SAVE.7-1 recipes.SAVE.7-4 tools.SAVE_RECIPE_TOOL.5
func TestCallToolACIDRecipesSave7_1And7_4AndToolsSaveRecipeTool5RequiresDraftIDAndCompleteContent(t *testing.T) {
	tests := []struct {
		name      string
		arguments SaveRecipeArguments
	}{
		{
			name:      "draft_id",
			arguments: SaveRecipeArguments{Source: "draft", DraftID: "   ", Markdown: "# Pasta", Portions: 2},
		},
		{
			name:      "markdown_without_portions",
			arguments: SaveRecipeArguments{Source: "draft", DraftID: testDraftID, Markdown: "# Pasta", Portions: 0},
		},
		{
			name:      "portions_without_markdown",
			arguments: SaveRecipeArguments{Source: "draft", DraftID: testDraftID, Markdown: "   ", Portions: 2},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			backend := &fakeBackend{}
			server := NewServer(backend, "test")

			_, _, err := server.callSaveRecipeTool(context.Background(), nil, tt.arguments)
			if err == nil {
				t.Fatal("callSaveRecipeTool() error = nil, want missing draft field error")
			}
			if backend.draftSaveCalls != 0 {
				t.Fatalf("draft save calls = %d, want none", backend.draftSaveCalls)
			}
			if backend.contentCalls != 0 {
				t.Fatalf("content calls = %d, want none", backend.contentCalls)
			}
			if backend.saveCalls != 0 || backend.updateCalls != 0 || backend.importRecipeURLCalls != 0 {
				t.Fatalf(
					"unexpected backend calls = prepared %d update %d import %d, want none",
					backend.saveCalls,
					backend.updateCalls,
					backend.importRecipeURLCalls,
				)
			}
		})
	}
}

// recipes.VALIDATION.1 recipes.VALIDATION.2
func TestCallToolACIDRecipesValidation1And2RejectsMalformedDraftOverwriteRecipeIDBeforeCooked(t *testing.T) {
	backend := &fakeBackend{}
	server := NewServer(backend, "test")

	_, _, err := server.callSaveRecipeTool(
		context.Background(),
		nil,
		SaveRecipeArguments{
			Source:   "draft",
			DraftID:  testDraftID,
			RecipeID: "not-a-uuid",
			Markdown: "# Pasta",
			Portions: 2,
		},
	)
	if err == nil {
		t.Fatal("callSaveRecipeTool() error = nil, want malformed recipe_id error")
	}
	if backend.updateCalls != 0 || backend.draftSaveCalls != 0 {
		t.Fatalf("update/draft save calls = %d/%d, want none", backend.updateCalls, backend.draftSaveCalls)
	}
}
