// Package jenkins implements the AgentTeam-native Jenkins Builtin Extension.
package jenkins

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 Revision = "jenkins-rest-v1"

// Extension composes the Jenkins catalog and connection-backed executor.
func Extension(credentials runner.CredentialSource) (extension.Extension, error) {
	if credentials == nil {
		return extension.Extension{}, fmt.Errorf(
			"jenkins: credential source is required")
	}
	executor := newExecutor(credentials, newClientCache(nil))
	return extension.Extension{
		Definition: Definition(),
		Implementation: runner.Implementation{
			Executor:            executor,
			Revision:            Revision,
			ConnectionPolicy:    registry.ConnectionRequired,
			Auth:                AuthSpec(),
			ValidateCredentials: executor.ValidateCredentials,
		},
		Distribution: extension.Distribution{
			AppID:          "app_jenkins",
			AppSlug:        Domain,
			DefaultName:    Domain,
			DefaultEnabled: true,
			IconURL:        "/app-icons/jenkins.svg",
		},
	}, nil
}

// AuthSpec declares the self-hosted Jenkins URL plus dynamic username/API-token
// Basic authentication. Only apiToken is secret in the connection UI.
func AuthSpec() runner.AuthSpec {
	return runner.AuthSpec{
		Kind:               runner.AuthBasic,
		BasicUsernameField: "username",
		BasicPasswordField: "apiToken",
		Fields: []runner.CredentialField{
			{
				Name:        "baseUrl",
				DisplayName: "Jenkins URL",
				Description: "HTTPS URL of the Jenkins instance.",
				Required:    true,
			},
			{
				Name:        "username",
				DisplayName: "Jenkins username",
				Description: "Jenkins user associated with the API token.",
				Required:    true,
			},
			{
				Name:        "apiToken",
				DisplayName: "Jenkins API token",
				Description: "Jenkins user API token; account passwords are not supported.",
				Required:    true,
				Secret:      true,
			},
		},
	}
}

func (executor *Executor) ValidateCredentials(
	ctx context.Context,
	credentials runner.Credentials,
) error {
	target, err := executor.resolveClient(credentials)
	if err != nil {
		return err
	}
	query := url.Values{
		"tree": []string{"name,authenticated,anonymous"},
	}
	response, err := target.rest.CallResponse(ctx, credentials, runner.RESTRequest{
		Method: http.MethodGet,
		Path:   "/whoAmI/api/json",
		Query:  query,
		Response: runner.RESTResponseSpec{
			Mode:           runner.RESTResponseJSON,
			MaxBytes:       64 << 10,
			ExposedHeaders: []string{"X-Jenkins"},
		},
	})
	if err != nil {
		return err
	}
	var identity struct {
		Name          string `json:"name"`
		Authenticated bool   `json:"authenticated"`
		Anonymous     bool   `json:"anonymous"`
	}
	if err := json.Unmarshal(response.Body, &identity); err != nil {
		return runner.Errorf(
			runner.ErrorUpstreamFailure,
			"Jenkins identity response was malformed",
			err,
		)
	}
	if !identity.Authenticated || identity.Anonymous || identity.Name == "" {
		return runner.Errorf(
			runner.ErrorProviderAuthUnavailable,
			"Jenkins rejected the connection credentials",
			nil,
		)
	}
	return nil
}
