package members_client_test

import (
	"bytes"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"testing"

	"code.tnxs.net/vernonkeenan/lib/api/members/members_client/prompts"
	"code.tnxs.net/vernonkeenan/lib/api/members/members_models"
	openapiruntime "github.com/go-openapi/runtime"
	"github.com/go-openapi/strfmt"
)

var promptTestAuth = openapiruntime.ClientAuthInfoWriterFunc(func(openapiruntime.ClientRequest, strfmt.Registry) error {
	return nil
})

type promptCaptureTransport struct {
	operation *openapiruntime.ClientOperation
}

func (transport *promptCaptureTransport) Submit(operation *openapiruntime.ClientOperation) (any, error) {
	return transport.SubmitContext(context.Background(), operation)
}

func (transport *promptCaptureTransport) SubmitContext(_ context.Context, operation *openapiruntime.ClientOperation) (any, error) {
	transport.operation = operation

	switch operation.ID {
	case "getPrompts":
		return prompts.NewGetPromptsOK(), nil
	case "postPrompts":
		return prompts.NewPostPromptsOK(), nil
	case "putPrompts":
		return prompts.NewPutPromptsOK(), nil
	case "getPromptAnswers":
		return prompts.NewGetPromptAnswersOK(), nil
	case "postPromptAnswers":
		return prompts.NewPostPromptAnswersOK(), nil
	case "getPromptCategories":
		return prompts.NewGetPromptCategoriesOK(), nil
	case "postPromptCategories":
		return prompts.NewPostPromptCategoriesOK(), nil
	case "putPromptCategories":
		return prompts.NewPutPromptCategoriesOK(), nil
	case "getPromptTags":
		return prompts.NewGetPromptTagsOK(), nil
	case "postPromptTags":
		return prompts.NewPostPromptTagsOK(), nil
	case "putPromptTags":
		return prompts.NewPutPromptTagsOK(), nil
	default:
		panic("unexpected generated prompt operation: " + operation.ID)
	}
}

func TestGeneratedPromptOperationContract(t *testing.T) {
	type operationCall func(openapiruntime.ContextualTransport) error

	tests := []struct {
		name       string
		wantID     string
		wantMethod string
		wantPath   string
		call       operationCall
	}{
		{
			name: "list prompts", wantID: "getPrompts", wantMethod: "GET", wantPath: "/prompts",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).GetPrompts(prompts.NewGetPromptsParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "create prompt", wantID: "postPrompts", wantMethod: "POST", wantPath: "/prompts",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PostPrompts(prompts.NewPostPromptsParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "update prompt", wantID: "putPrompts", wantMethod: "PUT", wantPath: "/prompts",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PutPrompts(prompts.NewPutPromptsParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "list prompt answers", wantID: "getPromptAnswers", wantMethod: "GET", wantPath: "/promptanswers",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).GetPromptAnswers(prompts.NewGetPromptAnswersParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "append prompt answer", wantID: "postPromptAnswers", wantMethod: "POST", wantPath: "/promptanswers",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PostPromptAnswers(prompts.NewPostPromptAnswersParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "list prompt categories", wantID: "getPromptCategories", wantMethod: "GET", wantPath: "/promptcategories",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).GetPromptCategories(prompts.NewGetPromptCategoriesParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "create prompt category", wantID: "postPromptCategories", wantMethod: "POST", wantPath: "/promptcategories",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PostPromptCategories(prompts.NewPostPromptCategoriesParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "update prompt category", wantID: "putPromptCategories", wantMethod: "PUT", wantPath: "/promptcategories",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PutPromptCategories(prompts.NewPutPromptCategoriesParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "list prompt tags", wantID: "getPromptTags", wantMethod: "GET", wantPath: "/prompttags",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).GetPromptTags(prompts.NewGetPromptTagsParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "create prompt tag", wantID: "postPromptTags", wantMethod: "POST", wantPath: "/prompttags",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PostPromptTags(prompts.NewPostPromptTagsParams(), promptTestAuth)
				return err
			},
		},
		{
			name: "update prompt tag", wantID: "putPromptTags", wantMethod: "PUT", wantPath: "/prompttags",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := prompts.New(transport, strfmt.Default).PutPromptTags(prompts.NewPutPromptTagsParams(), promptTestAuth)
				return err
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			transport := &promptCaptureTransport{}
			if err := test.call(transport); err != nil {
				t.Fatalf("generated client operation failed: %v", err)
			}
			if transport.operation == nil {
				t.Fatal("generated client did not submit an operation")
			}
			if transport.operation.ID != test.wantID {
				t.Errorf("operation ID = %q, want %q", transport.operation.ID, test.wantID)
			}
			if transport.operation.Method != test.wantMethod {
				t.Errorf("method = %q, want %q", transport.operation.Method, test.wantMethod)
			}
			if transport.operation.PathPattern != test.wantPath {
				t.Errorf("path = %q, want %q", transport.operation.PathPattern, test.wantPath)
			}
			if transport.operation.AuthInfo == nil {
				t.Error("generated operation discarded its auth-info writer")
			}
		})
	}
}

func TestPromptSpecRequiresExactCompoundAuthentication(t *testing.T) {
	specText := readMembersPromptSpec(t)
	for _, definition := range []string{
		"  ApiKeyAuth:\n    type: \"apiKey\"\n    in: \"header\"\n    name: \"X-API-Key\"",
		"  kvSessionCookie:\n    name: Cookie\n    in: header\n    type: apiKey",
	} {
		if !strings.Contains(specText, definition) {
			t.Fatalf("authoritative spec is missing exact authentication definition:\n%s", definition)
		}
	}

	operations := map[string]map[string]string{
		"/prompts": {
			"GET":  "getPrompts",
			"POST": "postPrompts",
			"PUT":  "putPrompts",
		},
		"/promptanswers": {
			"GET":  "getPromptAnswers",
			"POST": "postPromptAnswers",
		},
		"/promptcategories": {
			"GET":  "getPromptCategories",
			"POST": "postPromptCategories",
			"PUT":  "putPromptCategories",
		},
		"/prompttags": {
			"GET":  "getPromptTags",
			"POST": "postPromptTags",
			"PUT":  "putPromptTags",
		},
	}

	for path, methods := range operations {
		for method, operationID := range methods {
			operation := promptSpecOperationBlock(t, specText, path, strings.ToLower(method))
			if !strings.Contains(operation, "      operationId: "+operationID+"\n") {
				t.Errorf("%s %s does not preserve operationId %q", method, path, operationID)
			}
			const compoundSecurity = "      security:\n        - ApiKeyAuth: []\n          kvSessionCookie: []"
			if strings.Count(operation, compoundSecurity) != 1 {
				t.Errorf("%s %s does not require the exact API-key + human-session compound security", method, path)
			}
		}
	}
}

func TestPromptModelHasExactFullFieldParity(t *testing.T) {
	want := map[string]string{
		"CreatedByID":        "*string",
		"CreatedDate":        "*string",
		"ID":                 "string",
		"Icon":               "*string",
		"ImageAltText":       "*string",
		"ImageURL":           "*string",
		"LastModifiedByID":   "*string",
		"LastModifiedDate":   "*string",
		"Logo":               "*string",
		"MaxTokens":          "*float64",
		"Model":              "*string",
		"Name":               "*string",
		"Order":              "*float64",
		"Parameters":         "*string",
		"Prompt":             "*string",
		"PromptCategoryID":   "*string",
		"ResearchProjectIDs": "[]string",
		"Slug":               "*string",
		"System":             "*string",
		"Tags":               "[]string",
		"Temperature":        "*float64",
		"TenantID":           "*string",
		"Title":              "*string",
		"UsedCount":          "*float64",
		"UserID":             "*string",
	}

	model := reflect.TypeOf(members_models.Prompt{})
	if model.NumField() != len(want) {
		t.Fatalf("Prompt has %d fields, want exact authoritative set of %d", model.NumField(), len(want))
	}
	for name, wantType := range want {
		field, exists := model.FieldByName(name)
		if !exists {
			t.Errorf("Prompt is missing authoritative field %s", name)
			continue
		}
		if field.Type.String() != wantType {
			t.Errorf("Prompt.%s type = %s, want %s", name, field.Type, wantType)
		}
		if got := strings.Split(field.Tag.Get("json"), ",")[0]; got != name {
			t.Errorf("Prompt.%s JSON name = %q, want %q", name, got, name)
		}
	}

	maxTokens := 4096.0
	body, err := json.Marshal(members_models.Prompt{MaxTokens: &maxTokens})
	if err != nil {
		t.Fatalf("marshal Prompt: %v", err)
	}
	if !bytes.Contains(body, []byte(`"MaxTokens":4096`)) {
		t.Fatalf("Prompt JSON lost MaxTokens: %s", body)
	}
}

func TestPromptAnswerIsAppendOnlyAndLegacyMisspellingIsAbsent(t *testing.T) {
	contract := reflect.TypeOf((*prompts.ClientService)(nil)).Elem()
	for _, method := range []string{
		"PutPromptAnswer",
		"PutPromptAnswerContext",
		"PutPromptAnswers",
		"PutPromptAnswersContext",
		"PutPromptAnsweer",
		"PutPromptAnsweerContext",
		"PutPromptAnsweers",
		"PutPromptAnsweersContext",
	} {
		if _, exists := contract.MethodByName(method); exists {
			t.Errorf("append-only PromptAnswer client exposes forbidden update %s", method)
		}
	}

	answerPath := promptSpecPathBlock(t, readMembersPromptSpec(t), "/promptanswers")
	for _, forbidden := range []string{"    put:", "    patch:", "    delete:"} {
		if strings.Contains(answerPath, forbidden) {
			t.Fatalf("authoritative PromptAnswer contract exposes forbidden operation %q", strings.TrimSpace(forbidden))
		}
	}
}

func TestPromptUpdateClientsPreserveConflictResponses(t *testing.T) {
	conflicts := []interface{ IsCode(int) bool }{
		prompts.NewPutPromptsConflict(),
		prompts.NewPutPromptCategoriesConflict(),
		prompts.NewPutPromptTagsConflict(),
	}
	for _, conflict := range conflicts {
		if !conflict.IsCode(409) {
			t.Errorf("%T does not preserve the optimistic-conflict response", conflict)
		}
	}
}

func TestPromptProviderContractKeepsExternalMembersSpecAligned(t *testing.T) {
	repoRoot := promptRepoRoot(t)
	mainSpec, err := os.ReadFile(filepath.Join(repoRoot, "swagger", "members-vernonkeenan.yaml"))
	if err != nil {
		t.Fatalf("read Members spec: %v", err)
	}

	externalSpec, err := os.ReadFile(filepath.Join(repoRoot, "swagger", "external", "members-vernonkeenan.yaml"))
	if err != nil {
		t.Fatalf("read external Members spec: %v", err)
	}
	normalized := bytes.ReplaceAll(externalSpec, []byte(`"https"`), []byte(`"http"`))
	normalized = bytes.ReplaceAll(normalized, []byte("gw.tnxs.net"), []byte("members.vernonkeenan.com:8080"))
	normalized = bytes.ReplaceAll(normalized, []byte(`"/vk/members/v1"`), []byte(`"/v1"`))
	if !bytes.Equal(normalized, mainSpec) {
		t.Fatal("external Members spec differs from the conventional host/scheme/base-path rewrite")
	}
}

func TestGeneratedPromptSurfaceHasNoSalesforceOrEmbeddedSecrets(t *testing.T) {
	repoRoot := promptRepoRoot(t)
	targets := []string{
		filepath.Join(repoRoot, "api", "members", "members_client", "prompts"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_request.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_response.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer_request.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer_response.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category_request.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category_response.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag_request.go"),
		filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag_response.go"),
	}
	credentialLiteral := regexp.MustCompile(`(?i)(api[_-]?key|password|secret)\s*[:=]\s*["'][^"']+["']`)

	for _, target := range targets {
		err := filepath.WalkDir(target, func(path string, entry os.DirEntry, walkErr error) error {
			if walkErr != nil {
				return walkErr
			}
			if entry.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
				return nil
			}

			content, err := os.ReadFile(path)
			if err != nil {
				return err
			}
			lower := strings.ToLower(string(content))
			for _, forbidden := range []string{"salesforce", "sf-gate", "go-force", "private key-----"} {
				if strings.Contains(lower, forbidden) {
					t.Errorf("%s contains forbidden boundary %q", path, forbidden)
				}
			}
			if credentialLiteral.Match(content) {
				t.Errorf("%s contains a credential-shaped literal", path)
			}
			return nil
		})
		if err != nil {
			t.Fatalf("scan generated prompt target %s: %v", target, err)
		}
	}
}

func readMembersPromptSpec(t *testing.T) string {
	t.Helper()
	content, err := os.ReadFile(filepath.Join(promptRepoRoot(t), "swagger", "members-vernonkeenan.yaml"))
	if err != nil {
		t.Fatalf("read Members Swagger: %v", err)
	}
	return string(content)
}

func promptSpecPathBlock(t *testing.T, specText, path string) string {
	t.Helper()
	startMarker := "  " + path + ":\n"
	start := strings.Index(specText, startMarker)
	if start < 0 {
		t.Fatalf("authoritative spec is missing path %s", path)
	}
	block := specText[start+len(startMarker):]
	if end := strings.Index(block, "\n  /"); end >= 0 {
		block = block[:end]
	}
	return block
}

func promptSpecOperationBlock(t *testing.T, specText, path, method string) string {
	t.Helper()
	pathBlock := promptSpecPathBlock(t, specText, path)
	startMarker := "    " + method + ":\n"
	start := strings.Index(pathBlock, startMarker)
	if start < 0 {
		t.Fatalf("authoritative spec is missing %s %s", strings.ToUpper(method), path)
	}
	block := pathBlock[start+len(startMarker):]
	methodBoundary := regexp.MustCompile(`(?m)^    [a-z]+:\n`)
	if end := methodBoundary.FindStringIndex(block); end != nil {
		block = block[:end[0]]
	}
	return block
}

func promptRepoRoot(t *testing.T) string {
	t.Helper()
	_, testFile, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve test source path")
	}
	return filepath.Clean(filepath.Join(filepath.Dir(testFile), "..", "..", ".."))
}
