package common

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"

	"github.com/go-logr/logr"
)

// Task status constants returned by the replayer API
const (
	// TaskStatusRunning indicates the task is currently running
	TaskStatusRunning = "running"
	// TaskStatusCanceled indicates the task was canceled
	TaskStatusCanceled = "canceled"
	// TaskStatusDone indicates the task completed successfully
	TaskStatusDone = "done"
)

// Task type constants
const (
	// TaskTypeReplay indicates a replay task
	TaskTypeReplay = "replay"
	// TaskTypeCapture indicates a capture task
	TaskTypeCapture = "capture"
)

// ReplayTaskStatus represents the status of a replay task returned by the API
// This is returned by both /api/traffic/cancel and /api/traffic/show endpoints
type ReplayTaskStatus struct {
	Type      string  `json:"type"`                  // "replay" or "capture" (use TaskTypeReplay/TaskTypeCapture constants)
	Status    string  `json:"status"`                // "canceled", "done", or "running" (use TaskStatus* constants)
	StartTime string  `json:"start_time"`            // RFC3339 format
	EndTime   string  `json:"end_time,omitempty"`    // RFC3339 format
	Progress  string  `json:"progress"`              // e.g., "0%" (always 0 for audit log format)
	Error     string  `json:"error,omitempty"`       // Error message when stopped
	LastCmdTs string  `json:"last_cmd_ts,omitempty"` // Start timestamp of latest command
	Input     string  `json:"input"`                 // Input file path or S3 URL
	Username  string  `json:"username"`              // TiDB username
	Format    string  `json:"format"`                // "audit_log_plugin" or "native"
	Speed     float64 `json:"speed,omitempty"`       // Replay speed multiplier
	ReadOnly  bool    `json:"readonly,omitempty"`    // Whether only replaying read SQLs
	Addr      string  `json:"addr,omitempty"`        // TiDB endpoint address
}

// CreateTaskRequest contains parameters for creating a replay task
// Reference: replay.md - POST /api/traffic/replay
type CreateTaskRequest struct {
	Addr            string            `json:"addr"`           // Required: TiDB endpoint, such as 127.0.0.1:4000
	Input           string            `json:"input"`          // Required: S3 path or local file path
	Speed           float64           `json:"speed"`          // Default: 1.0
	StartTime       string            `json:"start-time"`     // RFC3339 format, default: time.Now()
	Username        string            `json:"username"`       // Required
	Password        string            `json:"password"`       // Required
	Format          string            `json:"format"`         // Required: audit_log_plugin or native
	ReadOnly        bool              `json:"readonly"`       // Default: false
	IgnoreErrs      bool              `json:"ignore-errs"`    // Default: false
	CmdStartTime    string            `json:"cmdstarttime"`   // RFC3339 or RFC3339Nano format
	CmdEndTime      string            `json:"cmdendtime"`     // RFC3339 or RFC3339Nano format
	BufSize         int               `json:"bufsize"`        // Default: 0 for API
	PsClose         string            `json:"ps-close"`       // Default: directed (directed/always/never)
	CheckpointPath  string            `json:"checkpointpath"` // Checkpoint file path
	DryRun          bool              `json:"dryrun"`         // run in dry-run mode
	DynamicInput    bool              `json:"dynamicinput"`   // Optional Whether to dynamically load new directorioes.
	ReplayerCount   int32             `json:"replayercount"`  // Optional The total count of replayers. It's only needed when dynamicinput is true. It's used to cordinate the replayer directories to replayer.
	ReplayerIndex   int32             `json:"replayerindex"`  // Optional The index of the current replayer. Start from 0. It should be less than the replayercount.
	OutputPath      string            `json:"outputpath"`     // Optional The file path to store replayed sql. Empty indicates do not output replayed sql.
	CustomArguments map[string]string `json:"customArguments"`
}

// CancelTaskRequest contains parameters for canceling a replay task
// Reference: replay.md - POST /api/traffic/cancel
type CancelTaskRequest struct {
	Type     string `json:"type"`     // "capture" or "replay", empty means both
	Graceful bool   `json:"graceful"` // Default: false
}

// HttpLink is the interface for HTTP communication (same as k8s.HttpLink)
// Used for cross-cluster access via Kubernetes API proxy (Pod or Service proxy)
type HttpLink interface {
	Get(string) ([]byte, error)
	Post(string, interface{}) ([]byte, error)
}

// ReplayClient provides HTTP API access to replayer pods via Kubernetes proxy
// Only supports cross-cluster access via HttpLink (Pod/Service proxy)
type ReplayClient struct {
	httpLink HttpLink
}

// NewReplayClientViaKubeProxy creates a ReplayClient that uses Kubernetes proxy
// to access pods in a different cluster. This is the ONLY way to create a ReplayClient.
func NewReplayClientViaKubeProxy(httpLink HttpLink) *ReplayClient {
	return &ReplayClient{
		httpLink: httpLink,
	}
}

// CreateTask creates a replay task via HTTP API using Kubernetes proxy
// POST /api/traffic/replay with application/x-www-form-urlencoded
func (c *ReplayClient) CreateTask(req CreateTaskRequest, logger logr.Logger) error {
	// Build form data as URL-encoded string
	// The k8s.HttpLink.Post() accepts interface{} and converts to string

	formData := url.Values{}

	// Custom arguments have higher priority than the optional fields
	for key, value := range req.CustomArguments {
		addUrlValueIfNotExists(formData, key, value)
	}

	addUrlValueIfNotExists(formData, "addr", req.Addr)
	addUrlValueIfNotExists(formData, "input", req.Input)
	addUrlValueIfNotExists(formData, "username", req.Username)
	addUrlValueIfNotExists(formData, "password", req.Password)
	addUrlValueIfNotExists(formData, "format", req.Format)

	// Add optional fields
	if req.Speed != 0 {
		addUrlValueIfNotExists(formData, "speed", fmt.Sprintf("%.2f", req.Speed))
	}
	if req.StartTime != "" {
		addUrlValueIfNotExists(formData, "start-time", req.StartTime)
	}
	if req.ReadOnly {
		addUrlValueIfNotExists(formData, "readonly", "true")
	}
	if req.IgnoreErrs {
		addUrlValueIfNotExists(formData, "ignore-errs", "true")
	}
	if req.CmdStartTime != "" {
		addUrlValueIfNotExists(formData, "cmdstarttime", req.CmdStartTime)
	}
	if req.CmdEndTime != "" {
		addUrlValueIfNotExists(formData, "cmdendtime", req.CmdEndTime)
	}
	if req.BufSize > 0 {
		addUrlValueIfNotExists(formData, "bufsize", fmt.Sprintf("%d", req.BufSize))
	}
	if req.PsClose != "" {
		addUrlValueIfNotExists(formData, "ps-close", req.PsClose)
	}
	if req.CheckpointPath != "" {
		addUrlValueIfNotExists(formData, "checkpointpath", req.CheckpointPath)
	}
	if req.DryRun {
		addUrlValueIfNotExists(formData, "dryrun", "true")
	}
	if req.DynamicInput {
		addUrlValueIfNotExists(formData, "dynamicinput", "true")
	}
	if req.OutputPath != "" {
		addUrlValueIfNotExists(formData, "outputpath", req.OutputPath)
	}
	if formData.Get("dynamicinput") == "true" {
		if req.ReplayerCount > 0 {
			addUrlValueIfNotExists(formData, "replayercount", fmt.Sprintf("%d", req.ReplayerCount))
		}
		if req.ReplayerIndex >= 0 {
			addUrlValueIfNotExists(formData, "replayerindex", fmt.Sprintf("%d", req.ReplayerIndex))
		}
	}

	logger.Info("Create Replay task request", "formData", formData.Encode())

	// Call HTTP API via kubeproxy
	_, err := c.httpLink.Post("/api/traffic/replay", formData.Encode())
	if err != nil {
		// If a job is already running, the replayer returns HTTP 500 with "a job is running" message
		// This is not an error for idempotent operations, so we treat it as success
		if strings.Contains(err.Error(), "job is running") {
			return nil
		}
		return fmt.Errorf("kubeproxy POST failed: %w", err)
	}

	// HTTP 200 means success, regardless of response content
	return nil
}

// CancelTask cancels a replay task via HTTP API using Kubernetes proxy
// POST /api/traffic/cancel with application/x-www-form-urlencoded
// Returns the task status before cancellation
func (c *ReplayClient) CancelTask(req CancelTaskRequest) (*ReplayTaskStatus, error) {
	// Build form data
	formData := ""
	if req.Type != "" {
		formData = fmt.Sprintf("type=%s", url.QueryEscape(req.Type))
	}
	if req.Graceful {
		if formData != "" {
			formData += "&"
		}
		formData += "graceful=true"
	}

	// Call HTTP API via kubeproxy
	_, err := c.httpLink.Post("/api/traffic/cancel", formData)
	if err != nil {
		return nil, fmt.Errorf("kubeproxy POST failed: %w", err)
	}

	// HTTP 200 means success, regardless of response content
	return nil, nil
}

// ShowTasks retrieves the status of all tasks (both replay and capture)
// GET /api/traffic/show
// Returns an array of task statuses
func (c *ReplayClient) ShowTasks() ([]ReplayTaskStatus, error) {
	// Call HTTP API via kubeproxy
	respBody, err := c.httpLink.Get("/api/traffic/show")
	if err != nil {
		return nil, fmt.Errorf("kubeproxy GET failed: %w", err)
	}

	// Expected response: JSON array of task statuses
	var tasks []ReplayTaskStatus
	if err := json.Unmarshal(respBody, &tasks); err != nil {
		return nil, fmt.Errorf("failed to parse response JSON: %w", err)
	}

	return tasks, nil
}

// GetTaskStatus retrieves the current status of a specific replay task
// This is a convenience wrapper around ShowTasks that filters for replay tasks
func (c *ReplayClient) GetTaskStatus() (*ReplayTaskStatus, error) {
	tasks, err := c.ShowTasks()
	if err != nil {
		return nil, err
	}

	// Find the last replay task (there should only be one per pod, but replayer can't)
	for i := len(tasks) - 1; i >= 0; i-- {
		if tasks[i].Type == TaskTypeReplay {
			return &tasks[i], nil
		}
	}

	return nil, fmt.Errorf("no replay task found")
}

func addUrlValueIfNotExists(formData url.Values, key string, value string) {
	if formData.Has(key) {
		return
	}
	formData.Add(key, value)
}
