// Package tianyancha registers the Tianyancha Builtin Extension: the
// enterprise due-diligence Open API (company registry, risk, judicial, IP,
// tenders, and news) over one fixed origin with Authorization-token auth.
// Ported verbatim from OpenConnector provider tianyancha.
package tianyancha

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"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.
	Domain = "tianyancha"
	// Revision is the implementation identity folded into the runtime digest.
	Revision = "tianyancha-rest-v1"
	// BaseURL is the fixed Tianyancha Open API origin.
	BaseURL = "https://open.api.tianyancha.com"
	// validateSearchPath is the entry-level company-search route used by the
	// credential live-check.
	validateSearchPath = "/services/open/search/2.0"
)

// Extension composes the Tianyancha 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("tianyancha: 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_tianyancha",
			AppSlug:                   Domain,
			DefaultName:               Domain,
			DefaultEnabled:            true,
			IconURL:                   "/app-icons/tianyancha.svg",
			GenericProviderConnection: true,
		},
	}, nil
}

// AuthSpec declares the credential contract: the API token sent verbatim in
// the Authorization header (no Bearer prefix), exactly as the Open API
// expects.
func AuthSpec() runner.AuthSpec {
	return runner.AuthSpec{
		Kind:   runner.AuthHeader,
		Header: "Authorization",
		Fields: []runner.CredentialField{
			runner.APIKeyField(
				"Tianyancha API token",
				"The API token from Tianyancha Data Center > My APIs (https://open.tianyancha.com/), sent as the Authorization header.",
			),
		},
	}
}

// ValidateCredentials live-checks the token with one bounded entry-level
// company-search GET. Tianyancha has no token-introspection route, so a
// zero error_code envelope is the identity proof of a working credential.
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 validateCredentials(ctx, client, credentials)
}

// validateCredentials is the probe over one client, so wire tests can drive
// the exact production classification against a loopback double. A non-zero
// error_code is classified by the same closed table the actions use: an
// entitlement failure (a valid token that lacks the API package) must not
// look like a bad credential.
func validateCredentials(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
) error {
	query := url.Values{"word": []string{"百度"}}
	raw, err := client.Call(ctx, credentials, runner.RESTRequest{
		Method: http.MethodGet,
		Path:   validateSearchPath,
		Query:  query,
	})
	if err != nil {
		return err
	}
	var envelope struct {
		ErrorCode *json.Number `json:"error_code"`
	}
	if err := json.Unmarshal(raw, &envelope); err != nil || envelope.ErrorCode == nil {
		return runner.Errorf(
			runner.ErrorUpstreamFailure, "tianyancha returned an invalid response", err)
	}
	code, err := envelope.ErrorCode.Int64()
	if err != nil {
		return runner.Errorf(
			runner.ErrorUpstreamFailure, "tianyancha returned an invalid response", err)
	}
	if code == 0 {
		return nil
	}
	classification, ok := classifyErrorCode(code)
	if !ok {
		classification = runner.ErrorUpstreamFailure
	}
	return runner.Errorf(classification, "tianyancha rejected the credentials", nil)
}
