// Package financialmodelingprep registers the Financial Modeling Prep
// Builtin Extension: quotes, historical prices, company profiles, financial
// statements, ratios, ratings, calendars, news, and technical indicators
// through the official stable API. Every action is one bounded GET whose
// apikey travels as a query parameter, exactly like the upstream
// OpenConnector executor; the credential stays connection-scoped and is
// injected into the query by the handlers.
package financialmodelingprep

import (
	"context"
	"fmt"

	"github.com/tidbcloud/AgentTeam/internal/extension"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
)

const (
	// Domain is the Extension domain (the kebab-case port of the upstream
	// service id financial_modeling_prep).
	Domain = "financial-modeling-prep"
	// Revision is the implementation identity folded into the runtime digest.
	Revision = "financial-modeling-prep-rest-v1"
	// BaseURL is the fixed Financial Modeling Prep stable API origin.
	BaseURL = "https://financialmodelingprep.com/stable"
	// APIKeyField is the connection credential carrying the API key.
	APIKeyField = "apiKey"
)

// Extension composes the Financial Modeling Prep Builtin. It fails closed
// when the provider client cannot be constructed.
func Extension(credentials runner.CredentialSource) (extension.Extension, error) {
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL:          BaseURL,
		Auth:             AuthSpec(),
		StatusClassifier: ClassifyStatus,
	})
	if err != nil {
		return extension.Extension{}, fmt.Errorf("financial-modeling-prep: build REST client: %w", err)
	}
	return extension.Extension{
		Definition: Definition(),
		Implementation: runner.Implementation{
			Executor:            runner.NewRESTExecutor(Domain, client, credentials, Handlers()),
			Revision:            Revision,
			ConnectionPolicy:    registry.ConnectionRequired,
			Auth:                AuthSpec(),
			ValidateCredentials: ValidateCredentials,
		},
		Distribution: extension.Distribution{
			AppID:                     "app_financial_modeling_prep",
			AppSlug:                   "financial-modeling-prep",
			DefaultName:               Domain,
			DefaultEnabled:            true,
			IconURL:                   "/app-icons/financial-modeling-prep.svg",
			GenericProviderConnection: true,
		},
	}, nil
}

// AuthSpec declares the Financial Modeling Prep credential contract: the API
// key sent as the apikey query parameter of every request, exactly like the
// official endpoint requires.
func AuthSpec() runner.AuthSpec {
	return runner.AuthSpec{
		Kind:      runner.AuthQuery,
		QueryName: "apikey",
		Fields: []runner.CredentialField{
			{
				Name:        APIKeyField,
				DisplayName: "Financial Modeling Prep API key",
				Description: "Financial Modeling Prep API key sent as the apikey query parameter. Create one at https://site.financialmodelingprep.com/developer/docs.",
				Required:    true,
				Secret:      true,
			},
		},
	}
}

// ValidateCredentials live-checks the key with one bounded AAPL quote probe,
// mirroring the upstream validation request exactly.
func ValidateCredentials(ctx context.Context, credentials runner.Credentials) error {
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL:          BaseURL,
		Auth:             AuthSpec(),
		StatusClassifier: ClassifyStatus,
	})
	if err != nil {
		return err
	}
	return ValidateCredentialsWithClientForTest(ctx, client, credentials)
}

// ValidateCredentialsWithClientForTest runs the credential probe against an
// injected client so wire tests exercise the exact validation request.
func ValidateCredentialsWithClientForTest(ctx context.Context, client *runner.RESTClient, credentials runner.Credentials) error {
	_, err := fetchQuote(ctx, client, credentials, "AAPL")
	return err
}

// ClassifyStatus maps Financial Modeling Prep HTTP responses onto the closed
// runtime error set. The stable API reports most failures inside bodies
// ({error}, {message}, {"Error Message"}); the query layer classifies those
// after parsing, and this classifier consults the same messages on
// HTTP-level failures.
func ClassifyStatus(status int, body []byte) runner.ErrorCode {
	if message, ok := bodyErrorMessage(body); ok {
		if code, marked := classifySpecificMessage(message); marked {
			return code
		}
	}
	switch status {
	case 401, 403:
		return runner.ErrorProviderAuthUnavailable
	case 402, 400, 404, 422:
		return runner.ErrorValidation
	case 429:
		return runner.ErrorRateLimited
	default:
		if status >= 500 && status < 600 {
			return runner.ErrorUpstreamFailure
		}
		if status >= 400 && status < 500 {
			return runner.ErrorValidation
		}
		return runner.ErrorUpstreamFailure
	}
}
