// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package openapi3gen

import (
	"strings"
	"testing"

	"github.com/getkin/kin-openapi/openapi3"
)

func TestDeriveEnumName_hit(t *testing.T) {
	key := EnumKey([]any{"red", "green", "blue"})
	astMap := map[string][]string{key: {"Color"}}
	usages := []enumUsage{{schemaName: "Paint", propName: "color"}}
	got, err := deriveEnumName(key, "", usages, astMap)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != "Color" {
		t.Fatalf("got %q, want %q", got, "Color")
	}
}

func TestDeriveEnumName_miss(t *testing.T) {
	key := EnumKey([]any{"x", "y"})
	usages := []enumUsage{{schemaName: "Thing", propName: "kind"}}
	_, err := deriveEnumName(key, "", usages, map[string][]string{})
	if err == nil {
		t.Fatal("expected miss error, got nil")
	}
	msg := err.Error()
	if !strings.Contains(msg, "Thing.kind") {
		t.Fatalf("error %q should list the missing usage", msg)
	}
	if !strings.Contains(msg, "swagger:enum") {
		t.Fatalf("error %q should hint at the fix", msg)
	}
}

func TestExtractSharedEnums_usesASTMap(t *testing.T) {
	doc := &openapi3.T{
		Components: &openapi3.Components{
			Schemas: openapi3.Schemas{
				"A": {Value: &openapi3.Schema{
					Type: &openapi3.Types{"object"},
					Properties: openapi3.Schemas{
						"color": {Value: &openapi3.Schema{
							Type: &openapi3.Types{"string"},
							Enum: []any{"red", "green", "blue"},
						}},
					},
				}},
				"B": {Value: &openapi3.Schema{
					Type: &openapi3.Types{"object"},
					Properties: openapi3.Schemas{
						"color": {Value: &openapi3.Schema{
							Type: &openapi3.Types{"string"},
							Enum: []any{"red", "green", "blue"},
						}},
					},
				}},
			},
		},
	}
	astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}}
	if err := extractSharedEnums(doc, astMap); err != nil {
		t.Fatalf("extractSharedEnums: %v", err)
	}
	if _, ok := doc.Components.Schemas["Color"]; !ok {
		t.Fatalf("expected Color schema to be extracted")
	}
}

func TestFixFileSchemas_recursesIntoNested(t *testing.T) {
	fileType := func() *openapi3.SchemaRef {
		return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"file"}}}
	}
	doc := &openapi3.T{
		Paths: openapi3.NewPaths(),
	}
	doc.Paths.Set("/upload", &openapi3.PathItem{
		Post: &openapi3.Operation{
			RequestBody: &openapi3.RequestBodyRef{
				Value: &openapi3.RequestBody{
					Content: openapi3.Content{
						"multipart/form-data": {
							Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{
								Type: &openapi3.Types{"object"},
								Properties: openapi3.Schemas{
									"attachment": fileType(),
									"items": {Value: &openapi3.Schema{
										Type:  &openapi3.Types{"array"},
										Items: fileType(),
									}},
									"alt": {Value: &openapi3.Schema{
										AllOf: openapi3.SchemaRefs{fileType()},
									}},
									"one": {Value: &openapi3.Schema{
										OneOf: openapi3.SchemaRefs{fileType()},
									}},
									"any": {Value: &openapi3.Schema{
										AnyOf: openapi3.SchemaRefs{fileType()},
									}},
									"not": {Value: &openapi3.Schema{
										Not: fileType(),
									}},
								},
							}},
						},
					},
				},
			},
			Responses: openapi3.NewResponses(),
		},
	})

	fixFileSchemas(doc)

	props := doc.Paths.Value("/upload").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties
	if !props["attachment"].Value.Type.Is("string") || props["attachment"].Value.Format != "binary" {
		t.Errorf("nested property not fixed: %+v", props["attachment"].Value)
	}
	if !props["items"].Value.Items.Value.Type.Is("string") || props["items"].Value.Items.Value.Format != "binary" {
		t.Errorf("array items not fixed: %+v", props["items"].Value.Items.Value)
	}
	if !props["alt"].Value.AllOf[0].Value.Type.Is("string") || props["alt"].Value.AllOf[0].Value.Format != "binary" {
		t.Errorf("allOf branch not fixed: %+v", props["alt"].Value.AllOf[0].Value)
	}
	if !props["one"].Value.OneOf[0].Value.Type.Is("string") || props["one"].Value.OneOf[0].Value.Format != "binary" {
		t.Errorf("oneOf branch not fixed: %+v", props["one"].Value.OneOf[0].Value)
	}
	if !props["any"].Value.AnyOf[0].Value.Type.Is("string") || props["any"].Value.AnyOf[0].Value.Format != "binary" {
		t.Errorf("anyOf branch not fixed: %+v", props["any"].Value.AnyOf[0].Value)
	}
	if !props["not"].Value.Not.Value.Type.Is("string") || props["not"].Value.Not.Value.Format != "binary" {
		t.Errorf("not branch not fixed: %+v", props["not"].Value.Not.Value)
	}
}

func TestExtractEnumTypeName_TeamVisibility(t *testing.T) {
	enum := []any{"public", "limited", "private"}
	key := EnumKey(enum)
	astMap := map[string][]string{key: {"UserVisibility", "TeamVisibility"}}
	schema := &openapi3.Schema{
		Type: &openapi3.Types{"string"},
		Enum: enum,
		Extensions: map[string]any{
			"x-go-enum-desc": "public TeamVisibilityPublic\nlimited TeamVisibilityLimited\nprivate TeamVisibilityPrivate",
		},
	}
	if got := extractEnumTypeName(schema, astMap); got != "TeamVisibility" {
		t.Fatalf("got %q, want %q", got, "TeamVisibility")
	}
}

func TestExtractEnumTypeName_ambiguousPrefixTie(t *testing.T) {
	enum := []any{"one", "two"}
	key := EnumKey(enum)
	astMap := map[string][]string{key: {"AB", "AC"}}
	schema := &openapi3.Schema{
		Type: &openapi3.Types{"string"},
		Enum: enum,
		Extensions: map[string]any{
			"x-go-enum-desc": "one ABOne\ntwo ACTwo",
		},
	}
	if got := extractEnumTypeName(schema, astMap); got != "" {
		t.Fatalf("got %q, want empty string for ambiguous tie", got)
	}
}

func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) {
	enum := []any{"a", "b"}
	key := EnumKey(enum)
	astMap := map[string][]string{key: {"Alpha", "Alphabet"}}
	schema := &openapi3.Schema{
		Type: &openapi3.Types{"string"},
		Enum: enum,
		Extensions: map[string]any{
			"x-go-enum-desc": "a AlphabetA\nb AlphabetB",
		},
	}
	if got := extractEnumTypeName(schema, astMap); got != "Alphabet" {
		t.Fatalf("got %q, want %q", got, "Alphabet")
	}
}

func TestExtractSharedEnums_missReturnsError(t *testing.T) {
	doc := &openapi3.T{
		Components: &openapi3.Components{
			Schemas: openapi3.Schemas{
				"A": {Value: &openapi3.Schema{
					Type: &openapi3.Types{"object"},
					Properties: openapi3.Schemas{
						"color": {Value: &openapi3.Schema{
							Type: &openapi3.Types{"string"},
							Enum: []any{"red", "green"},
						}},
					},
				}},
				"B": {Value: &openapi3.Schema{
					Type: &openapi3.Types{"object"},
					Properties: openapi3.Schemas{
						"color": {Value: &openapi3.Schema{
							Type: &openapi3.Types{"string"},
							Enum: []any{"red", "green"},
						}},
					},
				}},
			},
		},
	}
	if err := extractSharedEnums(doc, map[string][]string{}); err == nil {
		t.Fatal("expected miss error")
	}
}
