package clinic_event

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"

	"sigs.k8s.io/controller-runtime/pkg/log"

	v1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/config"
)

var globalEventCli *EventClient

// EventCLient
type EventClient struct {
	client *http.Client
	config config.ActivityConfig `yaml:"activity"`
}

func InitEventClient(endpoint, source, sourceKey, clinicEndpoint, clinicToken string) error {
	if globalEventCli != nil {
		return nil
	}

	if source == "" || sourceKey == "" {
		return errors.New("init event client failed: source and source-key must be specified")
	}

	globalEventCli = &EventClient{
		client: &http.Client{},
		config: config.ActivityConfig{
			ActivityHubEndpoint:  endpoint,
			ActivityHubSource:    source,
			ActivityHubSourceKey: sourceKey,
			ClinicEndpoint:       clinicEndpoint,
			ClinicToken:          clinicToken,
		},
	}
	return nil
}

type EventRequest struct {
	Type            string                 `json:"type"`
	Name            string                 `json:"name"`
	SeverityLevel   int                    `json:"severity"`
	ApplicationID   string                 `json:"application_id"` // tenant id
	ConceptID       string                 `json:"concept_id"`     // project id
	TargetID        string                 `json:"target_id"`      //cluster id
	Catalog         string                 `json:"catalog"`        // event group
	CalibrationTime uint64                 `json:"calibration_time"`
	Author          string                 `json:"author"`
	ContextID       string                 `json:"context_id"`
	Context         []string               `json:"context"`
	ActivityID      string                 `json:"activity_id"` // ActivityID      is used to eliminate duplicate
	Payload         map[string]interface{} `json:"payload"`
}

func BuildGitopsEventTemplate(contextID, name string, cluster *v1.Cluster) *EventRequest {
	ev := &EventRequest{
		Type:            "",
		Name:            name,
		SeverityLevel:   int(Info),
		ApplicationID:   cluster.GetTenantID(),
		ConceptID:       cluster.GetProjectID(),
		TargetID:        cluster.GetClusterID(),
		Catalog:         GitopsCatalog,
		CalibrationTime: uint64(time.Now().Unix()),
		Author:          "",
		ContextID:       contextID,
		Context:         []string{},
		ActivityID:      fmt.Sprintf("%s-%s", contextID, name),
		Payload:         make(map[string]interface{}),
	}

	return ev
}

func BuildContextID(targetConfigRevisionName string) string {
	return fmt.Sprintf("%s-%s", GitopsCatalog, targetConfigRevisionName)
}

// SendEvent
func SendEvent(ctx context.Context, request *EventRequest) error {
	logger := log.FromContext(ctx)
	if globalEventCli == nil || globalEventCli.config.ActivityHubEndpoint == "" {
		return errors.New("global event client is not initialized")
	}
	if request.Type == "" {
		request.Type = "event"
	}

	logger.Info("try to send event to activity hub", "name", request.Name, "target", request.TargetID)
	data, err := json.Marshal(request)
	if err != nil {
		return err
	}
	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/activity", globalEventCli.config.ActivityHubEndpoint), bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	// add auth header
	req.Header.Add(XActivityHubSource, globalEventCli.config.ActivityHubSource)
	req.Header.Add(XActivityHubKey, globalEventCli.config.ActivityHubSourceKey)

	resp, err := globalEventCli.client.Do(req)
	if err != nil {
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	body, err := io.ReadAll(resp.Body)
	logger.Info("send event done", "status", resp.StatusCode, "body", string(body), "err", err)

	if resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("send event failed")
	}
	return nil
}

// RegisterClinicJob registers a job in clinic to periodically scan the config change status of the cluster
func RegisterClinicJob(ctx context.Context, clusterID, tenantID, contextID, jobType, summary string) error {
	logger := log.FromContext(ctx)

	if globalEventCli == nil || globalEventCli.config.ClinicEndpoint == "" {
		return errors.New("clinic endpoint must be specified")
	}

	path := fmt.Sprintf("%s/clinic/api/v1/activityhub/applications/%s/targets/%s/contexts/%s?type=%s&summary=%s",
		globalEventCli.config.ClinicEndpoint, tenantID, clusterID, url.PathEscape(contextID), url.QueryEscape(jobType), url.QueryEscape(summary))
	u, err := url.Parse(path)
	if err != nil {
		return fmt.Errorf("parse path: %w", err)
	}
	q := u.Query()
	u.RawQuery = q.Encode()

	req, err := http.NewRequest(http.MethodPost, u.String(), nil)
	if err != nil {
		return err
	}

	// add auth header
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", globalEventCli.config.ClinicToken))

	resp, err := globalEventCli.client.Do(req)
	if err != nil {
		return err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	body, err := io.ReadAll(resp.Body)
	logger.Info("register clinic job", "status", resp.StatusCode, "body", string(body), "err", err)

	if resp.StatusCode != http.StatusAccepted {
		return fmt.Errorf("register clinic job failed")
	}
	return nil
}
