package dedicated

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

	"github.com/smarty/assertions"
	"github.com/tidbcloud/observability/test/webconsolee2e/framework"
)

// normalizeTimeFormat normalizes time strings like "5m" to "5m0s" for comparison
func normalizeTimeFormat(timeStr string) string {
	if !strings.HasSuffix(timeStr, "s") && (strings.HasSuffix(timeStr, "m") || strings.HasSuffix(timeStr, "h")) {
		return timeStr + "0s"
	}
	return timeStr
}

// MonitorAlertRulesUpdateCase test case for updating monitor alert rules API
type MonitorAlertRulesUpdateCase struct{}

func NewMonitorAlertRulesUpdateCase() *MonitorAlertRulesUpdateCase {
	return &MonitorAlertRulesUpdateCase{}
}

func (c *MonitorAlertRulesUpdateCase) Name() string {
	return "MonitorAlertRulesUpdate"
}

func (c *MonitorAlertRulesUpdateCase) Description() string {
	return "Verify monitor alert rules update API (PUT) - /api/v1/external/{bizType}/orgs/{orgID}/projects/{projectID}/clusters/{clusterID}/monitor-alertrules"
}

// AlertRuleUpdateRequest request body for updating alert rules
type AlertRuleUpdateRequest struct {
	AlertRules map[string]AlertRuleUpdateItem `json:"alertRules"`
	Region     string                         `json:"region"`
	Vendor     string                         `json:"vendor"`
}

// AlertRuleUpdateItem represents a single alert rule update
type AlertRuleUpdateItem struct {
	For       int     `json:"for"`
	Threshold float64 `json:"threshold"`
}

// AlertRuleUpdateResponse response structure for alert rule update API
type AlertRuleUpdateResponse struct {
	Code    int                         `json:"code"`
	Message string                      `json:"message,omitempty"`
	Data    AlertRuleUpdateResponseData `json:"data"`
}

type AlertRuleUpdateResponseData struct {
	Data map[string]AlertRuleUpdateResult `json:"data"`
}

type AlertRuleUpdateResult struct {
	AlertName    string  `json:"alertName"`
	For          string  `json:"for"`
	OldFor       string  `json:"oldFor"`
	OldStatus    string  `json:"oldStatus"`
	OldThreshold float64 `json:"oldThreshold"`
	Status       string  `json:"status"`
	Threshold    float64 `json:"threshold"`
}

func (c *MonitorAlertRulesUpdateCase) Run(ctx *framework.TestContext) *framework.CaseResult {
	result := framework.NewCaseResult()

	// 1. Get the current 'for' value from SharedData (set by MonitorAlertRulesCase)
	forValue, ok := ctx.GetSharedData("alertrule_resources_45_for")
	if !ok {
		result.AddError("Failed to get alertrule_resources_45_for from SharedData. MonitorAlertRulesCase must run first.")
		return result
	}
	currentFor, ok := forValue.(int)
	if !ok {
		result.AddError(fmt.Sprintf("alertrule_resources_45_for is not an int: %T", forValue))
		return result
	}

	thresholdValue, ok := ctx.GetSharedData("alertrule_resources_45_threshold")
	if !ok {
		result.AddError("Failed to get alertrule_resources_45_threshold from SharedData")
		return result
	}
	currentThreshold, ok := thresholdValue.(float64)
	if !ok {
		result.AddError(fmt.Sprintf("alertrule_resources_45_threshold is not a float64: %T", thresholdValue))
		return result
	}

	// 2. Calculate new 'for' value (+5)
	newFor := currentFor + 5

	// 3. Parse regionID to get vendor and region
	vendor := ""
	region := ""
	if len(ctx.RegionID) > 0 {
		for i, ch := range ctx.RegionID {
			if ch == '-' {
				vendor = ctx.RegionID[:i]
				region = ctx.RegionID[i+1:]
				break
			}
		}
	}

	// 4. Build request body
	requestBody := AlertRuleUpdateRequest{
		AlertRules: map[string]AlertRuleUpdateItem{
			"customer/resources:45": {
				For:       newFor,
				Threshold: currentThreshold,
			},
		},
		Region: region,
		Vendor: vendor,
	}

	// 5. Build request URL
	apiURL := fmt.Sprintf("%s/api/v1/external/%s/orgs/%s/projects/%s/clusters/%s/monitor-alertrules",
		ctx.O11yGSURL,
		ctx.BizType,
		ctx.OrgID,
		ctx.ProjectID,
		ctx.ClusterID,
	)

	// Marshal request body for logging
	reqBodyBytes, _ := json.Marshal(requestBody)
	result.SetRequest("PUT", apiURL, requestBody)

	// 6. Send PUT request
	resp, err := ctx.Client.Put(apiURL, requestBody)
	if err != nil {
		result.AddError(fmt.Sprintf("Request failed: %v", err))
		return result
	}

	result.SetRequestWithHeaders(result.Request.Method, result.Request.URL, resp.RequestHeaders, result.Request.Body)
	result.SetResponseWithHeaders(resp.StatusCode, resp.GetResponseHeaders(), resp.String())

	// 7. Check common response headers (CORS) - use strict mode
	checker := framework.NewCommonResponseChecker(result, ctx.Origin)
	checker.CheckCORSHeaders(resp)
	// PUT request may return 200 or 202 (Accepted)
	if resp.StatusCode != 200 && resp.StatusCode != 202 {
		result.AddError(fmt.Sprintf("HTTP status code should be 200 or 202, got: %d", resp.StatusCode))
	}

	// 8. Parse response body
	var respBody AlertRuleUpdateResponse
	if err := json.Unmarshal(resp.Body, &respBody); err != nil {
		result.AddError(fmt.Sprintf("Failed to parse response JSON: %v", err))
		return result
	}

	// 9. Verify code is 0
	if msg := assertions.ShouldEqual(respBody.Code, 0); msg != "" {
		result.AddError(fmt.Sprintf("Response code should be 0: %s", msg))
	}

	// 10. Verify the update result
	updateResult, ok := respBody.Data.Data["customer/resources:45"]
	if !ok {
		result.AddError("Response should contain customer/resources:45 in data")
		return result
	}

	// 11. Verify oldFor matches the original value (format: "10m0s" for 10 minutes)
	// Normalize time format to handle both "10m" and "10m0s" formats
	expectedOldFor := fmt.Sprintf("%dm0s", currentFor)
	normalizedOldFor := normalizeTimeFormat(updateResult.OldFor)
	if msg := assertions.ShouldEqual(normalizedOldFor, expectedOldFor); msg != "" {
		result.AddError(fmt.Sprintf("OldFor should be %s: %s (Original: %s, Normalized: %s)", expectedOldFor, msg, updateResult.OldFor, normalizedOldFor))
	}

	// 12. Verify new 'for' matches our update value
	// Normalize time format to handle both "15m" and "15m0s" formats
	expectedNewFor := fmt.Sprintf("%dm0s", newFor)
	normalizedFor := normalizeTimeFormat(updateResult.For)
	if msg := assertions.ShouldEqual(normalizedFor, expectedNewFor); msg != "" {
		result.AddError(fmt.Sprintf("For should be %s: %s (Original: %s, Normalized: %s)", expectedNewFor, msg, updateResult.For, normalizedFor))
	}

	// 13. Restore the original value (cleanup)
	// This ensures the test is idempotent
	restoreBody := AlertRuleUpdateRequest{
		AlertRules: map[string]AlertRuleUpdateItem{
			"customer/resources:45": {
				For:       currentFor,
				Threshold: currentThreshold,
			},
		},
		Region: region,
		Vendor: vendor,
	}

	restoreResp, err := ctx.Client.Put(apiURL, restoreBody)
	if err != nil {
		result.AddError(fmt.Sprintf("Failed to restore original value: %v", err))
		return result
	}

	// PUT request may return 200 or 202 (Accepted)
	if restoreResp.StatusCode != 200 && restoreResp.StatusCode != 202 {
		result.AddError(fmt.Sprintf("Failed to restore original value, status: %d", restoreResp.StatusCode))
	}

	// Suppress unused variable warning
	_ = reqBodyBytes

	return result
}
