package research_client_test

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"testing"

	"code.tnxs.net/vernonkeenan/lib/api/research/research_client/factors"
	"code.tnxs.net/vernonkeenan/lib/api/research/research_client/financial_statements"
	"code.tnxs.net/vernonkeenan/lib/api/research/research_client/observations"
	"code.tnxs.net/vernonkeenan/lib/api/research/research_client/topics"
	openapiruntime "github.com/go-openapi/runtime"
	"github.com/go-openapi/strfmt"
)

const researchPR32SpecSHA256 = "c213fe19077453fcdc8f8ce3f6d1671cbbb5074f348c9e4ee0739f64ab0ebba3"

type insightCaptureTransport struct {
	operation *openapiruntime.ClientOperation
}

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

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

	switch operation.ID {
	case "getTopics":
		return topics.NewGetTopicsOK(), nil
	case "postTopics":
		return topics.NewPostTopicsOK(), nil
	case "putTopic":
		return topics.NewPutTopicOK(), nil
	case "getFactors":
		return factors.NewGetFactorsOK(), nil
	case "postFactors":
		return factors.NewPostFactorsOK(), nil
	case "putFactor":
		return factors.NewPutFactorOK(), nil
	case "getFinancialStatements":
		return financial_statements.NewGetFinancialStatementsOK(), nil
	case "postFinancialStatements":
		return financial_statements.NewPostFinancialStatementsOK(), nil
	case "putFinancialStatement":
		return financial_statements.NewPutFinancialStatementOK(), nil
	case "getObservations":
		return observations.NewGetObservationsOK(), nil
	case "postObservations":
		return observations.NewPostObservationsOK(), nil
	case "putObservation":
		return observations.NewPutObservationOK(), nil
	default:
		panic("unexpected generated insight operation: " + operation.ID)
	}
}

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

	tests := []struct {
		name       string
		wantID     string
		wantMethod string
		wantPath   string
		call       operationCall
	}{
		{
			name: "list topics", wantID: "getTopics", wantMethod: "GET", wantPath: "/topics",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := topics.New(transport, strfmt.Default).GetTopics(topics.NewGetTopicsParams(), nil)
				return err
			},
		},
		{
			name: "create topic", wantID: "postTopics", wantMethod: "POST", wantPath: "/topics",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := topics.New(transport, strfmt.Default).PostTopics(topics.NewPostTopicsParams(), nil)
				return err
			},
		},
		{
			name: "update topic", wantID: "putTopic", wantMethod: "PUT", wantPath: "/topics",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := topics.New(transport, strfmt.Default).PutTopic(topics.NewPutTopicParams(), nil)
				return err
			},
		},
		{
			name: "list factors", wantID: "getFactors", wantMethod: "GET", wantPath: "/factors",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := factors.New(transport, strfmt.Default).GetFactors(factors.NewGetFactorsParams(), nil)
				return err
			},
		},
		{
			name: "create factor", wantID: "postFactors", wantMethod: "POST", wantPath: "/factors",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := factors.New(transport, strfmt.Default).PostFactors(factors.NewPostFactorsParams(), nil)
				return err
			},
		},
		{
			name: "update factor", wantID: "putFactor", wantMethod: "PUT", wantPath: "/factors",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := factors.New(transport, strfmt.Default).PutFactor(factors.NewPutFactorParams(), nil)
				return err
			},
		},
		{
			name: "list financial statements", wantID: "getFinancialStatements", wantMethod: "GET", wantPath: "/financialstatements",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := financial_statements.New(transport, strfmt.Default).GetFinancialStatements(financial_statements.NewGetFinancialStatementsParams(), nil)
				return err
			},
		},
		{
			name: "create financial statement", wantID: "postFinancialStatements", wantMethod: "POST", wantPath: "/financialstatements",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := financial_statements.New(transport, strfmt.Default).PostFinancialStatements(financial_statements.NewPostFinancialStatementsParams(), nil)
				return err
			},
		},
		{
			name: "update financial statement", wantID: "putFinancialStatement", wantMethod: "PUT", wantPath: "/financialstatements",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := financial_statements.New(transport, strfmt.Default).PutFinancialStatement(financial_statements.NewPutFinancialStatementParams(), nil)
				return err
			},
		},
		{
			name: "list observations", wantID: "getObservations", wantMethod: "GET", wantPath: "/observations",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := observations.New(transport, strfmt.Default).GetObservations(observations.NewGetObservationsParams(), nil)
				return err
			},
		},
		{
			name: "create observation", wantID: "postObservations", wantMethod: "POST", wantPath: "/observations",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := observations.New(transport, strfmt.Default).PostObservations(observations.NewPostObservationsParams(), nil)
				return err
			},
		},
		{
			name: "update observation", wantID: "putObservation", wantMethod: "PUT", wantPath: "/observations",
			call: func(transport openapiruntime.ContextualTransport) error {
				_, err := observations.New(transport, strfmt.Default).PutObservation(observations.NewPutObservationParams(), nil)
				return err
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			transport := &insightCaptureTransport{}
			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)
			}
		})
	}
}

func TestGeneratedInsightGetByIDFiltersAndConflictResponses(t *testing.T) {
	recordID := "record-id"
	if got := topics.NewGetTopicsParams().WithTopicID(&recordID).TopicID; got == nil || *got != recordID {
		t.Fatal("Topic get-by-ID filter is absent")
	}
	if got := factors.NewGetFactorsParams().WithFactorID(&recordID).FactorID; got == nil || *got != recordID {
		t.Fatal("Factor get-by-ID filter is absent")
	}
	if got := financial_statements.NewGetFinancialStatementsParams().WithFinancialStatementID(&recordID).FinancialStatementID; got == nil || *got != recordID {
		t.Fatal("FinancialStatement get-by-ID filter is absent")
	}
	if got := observations.NewGetObservationsParams().WithObservationID(&recordID).ObservationID; got == nil || *got != recordID {
		t.Fatal("Observation get-by-ID filter is absent")
	}

	conflicts := []interface{ IsCode(int) bool }{
		topics.NewPutTopicConflict(),
		factors.NewPutFactorConflict(),
		financial_statements.NewPutFinancialStatementConflict(),
		observations.NewPutObservationConflict(),
	}
	for _, conflict := range conflicts {
		if !conflict.IsCode(409) {
			t.Errorf("%T does not preserve the optimistic-conflict response", conflict)
		}
	}
}

func TestGeneratedInsightSurfaceDoesNotInventUnsupportedOperations(t *testing.T) {
	contracts := []reflect.Type{
		reflect.TypeOf((*topics.ClientService)(nil)).Elem(),
		reflect.TypeOf((*factors.ClientService)(nil)).Elem(),
		reflect.TypeOf((*financial_statements.ClientService)(nil)).Elem(),
		reflect.TypeOf((*observations.ClientService)(nil)).Elem(),
	}
	for _, contract := range contracts {
		for index := 0; index < contract.NumMethod(); index++ {
			method := contract.Method(index).Name
			if strings.Contains(strings.ToLower(method), "bulk") {
				t.Errorf("%s invented unsupported bulk operation %s", contract, method)
			}
		}
	}

	topicContract := reflect.TypeOf((*topics.ClientService)(nil)).Elem()
	for _, method := range []string{"DeleteTopic", "DeleteTopicContext", "DeleteTopics", "DeleteTopicsContext"} {
		if _, exists := topicContract.MethodByName(method); exists {
			t.Errorf("Topic client invented unsupported operation %s", method)
		}
	}
}

func TestResearchSpecIsPinnedToProviderPR32Contract(t *testing.T) {
	repoRoot := insightRepoRoot(t)
	mainSpec, err := os.ReadFile(filepath.Join(repoRoot, "swagger", "research-vernonkeenan.yaml"))
	if err != nil {
		t.Fatalf("read Research spec: %v", err)
	}
	sum := sha256.Sum256(mainSpec)
	if got := hex.EncodeToString(sum[:]); got != researchPR32SpecSHA256 {
		t.Fatalf("Research spec drifted from provider PR #32: SHA-256 = %s, want %s", got, researchPR32SpecSHA256)
	}

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

func TestGeneratedInsightSurfaceHasNoSalesforceOrEmbeddedSecrets(t *testing.T) {
	repoRoot := insightRepoRoot(t)
	clientRoot := filepath.Join(repoRoot, "api", "research", "research_client")
	modelRoot := filepath.Join(repoRoot, "api", "research", "research_models")
	targets := []string{
		filepath.Join(clientRoot, "topics"),
		filepath.Join(clientRoot, "factors"),
		filepath.Join(clientRoot, "financial_statements"),
		filepath.Join(clientRoot, "observations"),
		filepath.Join(modelRoot, "topic.go"),
		filepath.Join(modelRoot, "topic_request.go"),
		filepath.Join(modelRoot, "topic_response.go"),
		filepath.Join(modelRoot, "factor.go"),
		filepath.Join(modelRoot, "factor_request.go"),
		filepath.Join(modelRoot, "factor_response.go"),
		filepath.Join(modelRoot, "financial_statement.go"),
		filepath.Join(modelRoot, "financial_statement_request.go"),
		filepath.Join(modelRoot, "financial_statement_response.go"),
		filepath.Join(modelRoot, "observation.go"),
		filepath.Join(modelRoot, "observation_request.go"),
		filepath.Join(modelRoot, "observation_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 contract target %s: %v", target, err)
		}
	}
}

func insightRepoRoot(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), "..", "..", ".."))
}
