// Package qichacha registers the Qichacha Builtin Extension: the enterprise
// due-diligence OpenAPI (registered shareholders and historical outbound
// investments) with the AppKey+SecretKey MD5 request-signature scheme.
// Ported verbatim from OpenConnector provider qichacha.
package qichacha

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 = "qichacha"
	// Revision is the implementation identity folded into the runtime digest.
	Revision = "qichacha-rest-v1"
	// BaseURL is the fixed Qichacha OpenAPI origin.
	BaseURL = "https://api.qichacha.com"
	// shareholdersPath serves list-company-shareholders.
	shareholdersPath = "/ECIPartner/GetList"
	// historicalInvestmentsPath serves list-company-historical-investments.
	historicalInvestmentsPath = "/HistoryInvestmentCheck/GetList"
)

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

// AuthSpec declares the credential contract: the AppKey and SecretKey of a
// Qichacha OpenAPI application. Both stay connection-scoped (no direct
// header injection): every request derives its Timespan and Token signature
// headers from the pair, and the AppKey also rides the key query parameter.
// Only the SecretKey is secret; the AppKey is a visible request parameter
// exactly as on the Qichacha wire.
func AuthSpec() runner.AuthSpec {
	return runner.AuthSpec{
		Kind: runner.AuthHeaders,
		Fields: []runner.CredentialField{
			{
				Name:        "appKey",
				DisplayName: "Qichacha AppKey",
				Description: "The Qichacha application AppKey from https://openapi.qcc.com/, sent as the key request parameter.",
				Required:    true,
			},
			{
				Name:        "secretKey",
				DisplayName: "Qichacha SecretKey",
				Description: "The Qichacha application SecretKey from https://openapi.qcc.com/, used to derive the request Token signature.",
				Required:    true,
				Secret:      true,
			},
		},
		Signature: signatureHeaders,
	}
}

// ValidateCredentials live-checks the credential pair with one bounded
// shareholder-list GET. A Status 200 envelope is the identity proof of a
// working credential; OrderNumber is nullable on this provider and carries
// no extra signal.
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 wire-testable core of ValidateCredentials: the
// probe request carries the same AppKey query construction as every action
// request, so a credential that passes the probe also behaves on the wire.
func validateCredentials(
	ctx context.Context, client *runner.RESTClient, credentials runner.Credentials,
) error {
	raw, err := client.Call(ctx, credentials, runner.RESTRequest{
		Method: http.MethodGet,
		Path:   shareholdersPath,
		Query:  credentialQuery(credentials, url.Values{"searchKey": []string{"验证"}}),
	})
	if err != nil {
		return err
	}
	var payload struct {
		Status  *string `json:"Status"`
		Message string  `json:"Message"`
	}
	if err := json.Unmarshal(raw, &payload); err != nil {
		return runner.Errorf(
			runner.ErrorUpstreamFailure, "qichacha returned an invalid response", err)
	}
	if payload.Status == nil {
		return runner.Errorf(
			runner.ErrorUpstreamFailure, "qichacha returned an invalid response", nil)
	}
	if *payload.Status != "200" {
		return businessError(payload.Message)
	}
	return nil
}
