package tianyancha

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

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

// ClassifyStatus maps Tianyancha responses onto the closed runtime error set.
// Tianyancha reports business failures through an error_code body field on
// any status, so the classifier consults the bounded body first and falls
// back to the shared status table.
func ClassifyStatus(status int, body []byte) runner.ErrorCode {
	if code, ok := classifyBodyErrorCode(body); ok {
		return code
	}
	switch {
	case status == http.StatusUnauthorized || status == http.StatusForbidden:
		return runner.ErrorProviderAuthUnavailable
	case status == http.StatusNotFound:
		return runner.ErrorNotFound
	case status == http.StatusTooManyRequests:
		return runner.ErrorRateLimited
	case status >= 400 && status < 500:
		return runner.ErrorValidation
	default:
		return runner.ErrorUpstreamFailure
	}
}

// classifyBodyErrorCode parses the bounded error body and delegates to the
// shared error_code table.
func classifyBodyErrorCode(body []byte) (runner.ErrorCode, bool) {
	if len(body) == 0 {
		return "", false
	}
	var envelope struct {
		ErrorCode *json.Number `json:"error_code"`
	}
	if err := json.Unmarshal(body, &envelope); err != nil || envelope.ErrorCode == nil {
		return "", false
	}
	code, err := envelope.ErrorCode.Int64()
	if err != nil {
		return "", false
	}
	return classifyErrorCode(code)
}

// classifyErrorCode maps the upstream error_code values onto the closed
// runtime error set: quota and frequency codes are rate limits, token codes
// are credential failures, entitlement codes are permission denials (a valid
// token that lacks the API package must not look like a bad credential),
// and the parameter code is a validation failure.
func classifyErrorCode(code int64) (runner.ErrorCode, bool) {
	switch code {
	case 300004, 300006, 300007:
		return runner.ErrorRateLimited, true
	case 300002, 300003, 300009:
		return runner.ErrorProviderAuthUnavailable, true
	case 300005, 300011:
		return runner.ErrorPermissionDenied, true
	case 300008:
		return runner.ErrorValidation, true
	default:
		return "", false
	}
}

// nullResultSentinel is the absent-data marker Tianyancha reports as
// error_code 300000.
const nullResultSentinel = 300000

// callAction performs one authenticated GET and unwraps the error_code
// envelope: zero returns the result payload, 300000 maps to JSON null, and
// every other code fails closed with its classified business error.
func callAction(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	path string,
	query url.Values,
) (json.RawMessage, error) {
	raw, err := client.Call(ctx, credentials, runner.RESTRequest{
		Method: http.MethodGet,
		Path:   path,
		Query:  query,
	})
	if err != nil {
		return nil, err
	}
	var envelope struct {
		ErrorCode *json.Number `json:"error_code"`
		Reason    string       `json:"reason"`
		Message   string       `json:"message"`
		Result    json.RawMessage
	}
	if err := json.Unmarshal(raw, &envelope); err != nil || envelope.ErrorCode == nil {
		return nil, runner.Errorf(
			runner.ErrorUpstreamFailure, "tianyancha returned an invalid response", nil)
	}
	code, err := envelope.ErrorCode.Int64()
	if err != nil {
		return nil, runner.Errorf(
			runner.ErrorUpstreamFailure, "tianyancha returned an invalid response", nil)
	}
	switch code {
	case 0:
		if envelope.Result == nil {
			return nil, runner.Errorf(
				runner.ErrorUpstreamFailure, "tianyancha returned no result payload", nil)
		}
		return envelope.Result, nil
	case nullResultSentinel:
		return json.RawMessage("null"), nil
	default:
		classification, ok := classifyErrorCode(code)
		if !ok {
			classification = runner.ErrorUpstreamFailure
		}
		message := envelope.Reason
		if message == "" {
			message = envelope.Message
		}
		if message == "" {
			message = "tianyancha rejected the request"
		}
		return nil, runner.Errorf(classification, message, nil)
	}
}
