package commerce

import (
	"context"
	"fmt"
	"net/url"
	"strings"
	"time"
)

type InstanceAdvancedRequest struct {
	Payload map[string]any `json:"payload"`
}

type instanceUpstreamSyncResult struct {
	Instance          Instance
	Payload           map[string]any
	Power             *string
	SecurityGroupID   any
	SecurityGroupName *string
	RootPassword      *string
}

func (s *Service) SyncInstanceUpstream(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	synced, err := s.syncInstanceUpstream(ctx, instance)
	if err != nil {
		return nil, err
	}
	return map[string]any{
		"instance": newInstanceSummaryForSync(synced.Instance),
		"upstream": synced.Payload,
	}, nil
}

func (s *Service) syncInstanceUpstream(ctx context.Context, instance Instance) (instanceUpstreamSyncResult, error) {
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, fmt.Sprintf("/clouds/%d", *instance.UpstreamID), "GET", nil)
	if err != nil {
		return instanceUpstreamSyncResult{}, err
	}
	payload := result.Payload
	if len(payload) == 0 {
		payload = result.Body
	}
	detail := instanceUpstreamDetail(payload)
	updates := map[string]any{"upstream_payload": jsonRawFromAny(sanitizeInstanceUpstreamPayload(payload))}
	if mainIP := strings.TrimSpace(firstString(detail["mainip"], detail["main_ip"], detail["dedicatedip"], detail["ip"])); mainIP != "" {
		updates["main_ip"] = mainIP
		instance.MainIP = &mainIP
	}
	if hostname := strings.TrimSpace(firstString(detail["hostname"], detail["domain"], detail["name"])); hostname != "" {
		updates["hostname"] = hostname
		instance.Hostname = &hostname
	}
	if port := intFromAny(detail["port"], detail["ssh_port"]); port != nil && *port > 0 && *port <= 65535 {
		updates["ssh_port"] = *port
		instance.SSHPort = port
	}
	for column, keys := range map[string][]any{
		"cpu":              {detail["cpu"]},
		"memory":           {detail["memory"]},
		"system_disk_size": {detail["system_disk_size"]},
		"data_disk_size":   {detail["data_disk_size"]},
		"in_bw":            {detail["in_bw"]},
		"out_bw":           {detail["out_bw"]},
		"nat_acl_limit":    {detail["nat_acl_limit"]},
	} {
		if value := intFromAny(keys...); value != nil {
			updates[column] = *value
			setSyncedInstanceInt(&instance, column, *value)
		}
	}
	if expiresAt := upstreamExpiry(firstNonNil(detail["nextduedate"], detail["next_due_date"], detail["expires_at"])); expiresAt != nil {
		updates["expires_at"] = *expiresAt
		instance.ExpiresAt = expiresAt
	}
	var rootPassword *string
	if rawPassword := strings.TrimSpace(firstString(detail["rootpassword"], detail["root_password"], detail["password"])); rawPassword != "" {
		encrypted, encryptErr := EncryptStoredSecret(rawPassword)
		if encryptErr != nil {
			return instanceUpstreamSyncResult{}, encryptErr
		}
		updates["root_password"] = encrypted
		instance.RootPassword = &encrypted
		rootPassword = &rawPassword
	}
	if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ? AND user_id = ?", instance.ID, instance.UserID).Updates(updates).Error; err != nil {
		return instanceUpstreamSyncResult{}, err
	}
	instance.UpstreamPayload = jsonRawFromAny(sanitizeInstanceUpstreamPayload(payload))
	instance.UpdatedAt = time.Now().UTC()
	securityID, securityName := upstreamSecurity(detail)
	return instanceUpstreamSyncResult{
		Instance:          instance,
		Payload:           sanitizeInstanceUpstreamPayload(payload),
		Power:             upstreamPowerLabel(firstString(detail["status"], detail["power"], detail["power_status"])),
		SecurityGroupID:   securityID,
		SecurityGroupName: securityName,
		RootPassword:      rootPassword,
	}, nil
}

func instanceUpstreamDetail(payload map[string]any) map[string]any {
	out := cloneMap(payload)
	for _, key := range []string{"host_data", "host", "cloud"} {
		if nested, ok := payload[key].(map[string]any); ok {
			for nestedKey, value := range nested {
				out[nestedKey] = value
			}
			break
		}
	}
	return out
}

func sanitizeInstanceUpstreamPayload(payload map[string]any) map[string]any {
	out := make(map[string]any, len(payload))
	for key, value := range payload {
		lower := strings.ToLower(strings.TrimSpace(key))
		if lower == "password" || lower == "rootpassword" || lower == "root_password" || lower == "ippassword" || lower == "new_password" {
			continue
		}
		switch typed := value.(type) {
		case map[string]any:
			out[key] = sanitizeInstanceUpstreamPayload(typed)
		case []any:
			items := make([]any, 0, len(typed))
			for _, item := range typed {
				if nested, ok := item.(map[string]any); ok {
					items = append(items, sanitizeInstanceUpstreamPayload(nested))
				} else {
					items = append(items, item)
				}
			}
			out[key] = items
		default:
			out[key] = value
		}
	}
	return out
}

func upstreamPowerLabel(value string) *string {
	status := strings.ToLower(strings.TrimSpace(value))
	labels := map[string]string{
		"on": "运行中", "running": "运行中", "active": "运行中",
		"off": "已关机", "stopped": "已关机",
		"init": "创建中", "provisioning": "创建中",
		"wait_reboot": "等待重启", "wait": "等待中", "pending": "等待中",
		"suspend": "已暂停", "suspended": "已暂停", "paused": "已挂起",
	}
	if status == "" {
		return nil
	}
	label := labels[status]
	if label == "" {
		label = status
	}
	return &label
}

func upstreamSecurity(detail map[string]any) (any, *string) {
	var id any
	var name *string
	security := detail["security"]
	if nested, ok := security.(map[string]any); ok {
		id = nested["id"]
		if value := strings.TrimSpace(firstString(nested["name"])); value != "" {
			name = &value
		}
	} else {
		id = security
	}
	if value := strings.TrimSpace(firstString(detail["security_name"])); value != "" {
		name = &value
	}
	if text := strings.TrimSpace(fmt.Sprint(id)); text == "" || text == "0" || text == "<nil>" {
		id = nil
	}
	return id, name
}

func upstreamExpiry(value any) *time.Time {
	if unix, ok := int64FromAny(value); ok && unix > 0 {
		parsed := time.Unix(unix, 0).UTC()
		return &parsed
	}
	text := strings.TrimSpace(fmt.Sprint(value))
	for _, layout := range []string{time.RFC3339, "2006-01-02 15:04:05", "2006-01-02"} {
		if parsed, err := time.Parse(layout, text); err == nil {
			parsed = parsed.UTC()
			return &parsed
		}
	}
	return nil
}

func setSyncedInstanceInt(instance *Instance, column string, value int) {
	switch column {
	case "cpu":
		instance.CPU = &value
	case "memory":
		instance.Memory = &value
	case "system_disk_size":
		instance.SystemDiskSize = &value
	case "data_disk_size":
		instance.DataDiskSize = &value
	case "in_bw":
		instance.InBW = &value
	case "out_bw":
		instance.OutBW = &value
	case "nat_acl_limit":
		instance.NatACLLimit = &value
	}
}

func (s *Service) InstanceRealData(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	return map[string]any{
		"instance_id":      instance.ID,
		"upstream_payload": jsonMap(instance.UpstreamPayload),
		"main_ip":          instance.MainIP,
		"hostname":         instance.Hostname,
		"ssh_port":         instance.SSHPort,
		"updated_at":       instance.UpdatedAt,
	}, nil
}

func (s *Service) FetchInstanceVNC(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, fmt.Sprintf("/clouds/%d/vnc", *instance.UpstreamID), "POST", nil)
	if err != nil {
		return nil, err
	}
	payload := result.Payload
	if len(payload) == 0 {
		payload = result.Body
	}
	return s.issueVNCTicket(ctx, userID, instance, payload)
}

func (s *Service) ResetInstancePassword(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	if payload == nil {
		payload = map[string]any{}
	}
	if _, ok := payload["password_type"]; !ok {
		payload["password_type"] = 0
	}
	return s.runAuditedInstancePath(ctx, userID, instanceID, "reset_password", "PUT", "password", payload, "user reset instance password")
}

func (s *Service) ReinstallInstance(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	if payload == nil {
		payload = map[string]any{}
	}
	if _, ok := payload["password_type"]; !ok {
		payload["password_type"] = 0
	}
	return s.runAuditedInstancePath(ctx, userID, instanceID, "reinstall", "PUT", "reinstall?image_client_hidden=0", payload, "user reinstall instance")
}

func (s *Service) ListInstanceNatACL(ctx context.Context, userID int64, instanceID int64, query url.Values) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	values := url.Values{}
	for _, key := range []string{"page", "per_page", "orderby", "sort", "search", "list_type"} {
		if value := strings.TrimSpace(query.Get(key)); value != "" {
			values.Set(key, value)
		}
	}
	path := fmt.Sprintf("/clouds/%d/nat_acl", *instance.UpstreamID)
	if encoded := values.Encode(); encoded != "" {
		path += "?" + encoded
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, path, "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) CreateInstanceNatACL(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	return s.runAuditedInstancePath(ctx, userID, instanceID, "nat_acl_create", "POST", "nat_acl", payload, "user create NAT ACL")
}

func (s *Service) DeleteInstanceNatACL(ctx context.Context, userID int64, instanceID int64, natACLID int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/nat_acl/%d?hostid=%d", natACLID, *instance.UpstreamID)
	return s.submitInstanceAdvancedAction(ctx, instance, "nat_acl_delete", "DELETE", path, nil, "user delete NAT ACL")
}

func (s *Service) ListInstanceSnapshots(ctx context.Context, userID int64, instanceID int64, query url.Values) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	values := url.Values{}
	for _, key := range []string{"page", "per_page", "orderby", "sort", "search", "type"} {
		if value := strings.TrimSpace(query.Get(key)); value != "" {
			values.Set(key, value)
		}
	}
	path := fmt.Sprintf("/clouds/%d/snapshots", *instance.UpstreamID)
	if encoded := values.Encode(); encoded != "" {
		path += "?" + encoded
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, path, "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) CreateInstanceSnapshot(ctx context.Context, userID int64, instanceID int64, diskID int64, payload map[string]any) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/disks/%d/snapshots?hostid=%d", diskID, *instance.UpstreamID)
	return s.submitInstanceAdvancedAction(ctx, instance, "snapshot_create", "POST", path, payload, "user create snapshot")
}

func (s *Service) DeleteInstanceSnapshot(ctx context.Context, userID int64, instanceID int64, snapshotID int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/snapshots/%d?hostid=%d", snapshotID, *instance.UpstreamID)
	return s.submitInstanceAdvancedAction(ctx, instance, "snapshot_delete", "DELETE", path, nil, "user delete snapshot")
}

func (s *Service) RestoreInstanceSnapshot(ctx context.Context, userID int64, instanceID int64, snapshotID int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/snapshots/%d/restore?hostid=%d", snapshotID, *instance.UpstreamID)
	return s.submitInstanceAdvancedAction(ctx, instance, "snapshot_restore", "POST", path, nil, "user restore snapshot")
}

func (s *Service) ListCronSnapshots(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, fmt.Sprintf("/clouds/%d/cron_snap", *instance.UpstreamID), "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) CreateCronSnapshot(ctx context.Context, userID int64, instanceID int64, payload map[string]any) (InstanceActionResponse, error) {
	return s.runAuditedInstancePath(ctx, userID, instanceID, "cron_snapshot_create", "POST", "cron_snap", payload, "user create cron snapshot")
}

func (s *Service) DeleteCronSnapshot(ctx context.Context, userID int64, instanceID int64, cronID int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/clouds/%d/cron_snap/%d", *instance.UpstreamID, cronID)
	return s.submitInstanceAdvancedAction(ctx, instance, "cron_snapshot_delete", "DELETE", path, nil, "user delete cron snapshot")
}

func (s *Service) GetInstanceFirewall(ctx context.Context, userID int64, instanceID int64) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, fmt.Sprintf("/clouds/%d/firewall", *instance.UpstreamID), "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) ListInstanceTasks(ctx context.Context, userID int64, instanceID int64, query url.Values) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	values := url.Values{}
	for _, key := range []string{"page", "per_page", "orderby", "sort"} {
		if value := strings.TrimSpace(query.Get(key)); value != "" {
			values.Set(key, value)
		}
	}
	path := fmt.Sprintf("/clouds/%d/tasks", *instance.UpstreamID)
	if encoded := values.Encode(); encoded != "" {
		path += "?" + encoded
	}
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, path, "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) InstanceMonitorChart(ctx context.Context, userID int64, instanceID int64, query url.Values) (map[string]any, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return nil, err
	}
	values := url.Values{}
	chartType := strings.TrimSpace(query.Get("type"))
	if chartType == "" {
		chartType = "cpu"
	}
	values.Set("type", chartType)
	for _, key := range []string{"select", "start", "end"} {
		if value := strings.TrimSpace(query.Get(key)); value != "" {
			values.Set(key, value)
		}
	}
	path := fmt.Sprintf("/clouds/%d/charts?%s", *instance.UpstreamID, values.Encode())
	result, err := s.fetchInstanceUpstreamPath(ctx, instance, path, "GET", nil)
	if err != nil {
		return nil, err
	}
	return upstreamResultMap(result), nil
}

func (s *Service) runAuditedInstancePath(ctx context.Context, userID int64, instanceID int64, action string, method string, suffix string, payload map[string]any, remark string) (InstanceActionResponse, error) {
	return s.runAuditedInstancePathForOrder(ctx, userID, instanceID, action, method, suffix, payload, remark, nil)
}

func (s *Service) runAuditedInstancePathForOrder(ctx context.Context, userID int64, instanceID int64, action string, method string, suffix string, payload map[string]any, remark string, orderID *int64) (InstanceActionResponse, error) {
	instance, err := s.userInstance(ctx, userID, instanceID)
	if err != nil {
		return InstanceActionResponse{}, err
	}
	product, _, productErr := s.productAndGroup(ctx, instance.ProductID)
	if productErr != nil {
		return InstanceActionResponse{}, productErr
	}
	if product != nil && product.APIType == "plugin" && product.APIIdentifier != nil {
		if !s.pluginActionAllowedOrMissing(*product, action) {
			return InstanceActionResponse{}, BadRequest("provision plugin does not support action: " + action)
		}
		log, err := s.submitPluginManageActionWithAudit(ctx, instance, *product, action, payload, "user", orderID, nil)
		return instanceActionResponseFromLog(log), err
	}
	if instance.UpstreamID == nil {
		return InstanceActionResponse{}, BadRequest("instance has no upstream resource")
	}
	path := fmt.Sprintf("/clouds/%d/%s", *instance.UpstreamID, suffix)
	return s.submitInstanceAdvancedActionForOrder(ctx, instance, action, method, path, payload, remark, orderID)
}

func (s *Service) submitInstanceAdvancedAction(ctx context.Context, instance Instance, action string, method string, path string, payload any, remark string) (InstanceActionResponse, error) {
	return s.submitInstanceAdvancedActionForOrder(ctx, instance, action, method, path, payload, remark, nil)
}

func (s *Service) submitInstanceAdvancedActionForOrder(ctx context.Context, instance Instance, action string, method string, path string, payload any, remark string, orderID *int64) (InstanceActionResponse, error) {
	log, err := s.submitUpstreamActionWithAudit(ctx, instance, action, "user", orderID, nil, method, path, payload, remark)
	return instanceActionResponseFromLog(log), err
}

func instanceActionResponseFromLog(log UpstreamActionLog) InstanceActionResponse {
	return InstanceActionResponse{
		Action: newUpstreamActionLogResponse(log),
		TaskID: log.UpstreamTaskID,
		Result: map[string]any{
			"status":  log.Status,
			"taskid":  log.UpstreamTaskID,
			"message": "action submitted",
		},
	}
}

func (s *Service) fetchInstanceUpstreamPath(ctx context.Context, instance Instance, path string, method string, payload any) (upstreamCallResult, error) {
	if instance.UpstreamID == nil {
		return upstreamCallResult{}, BadRequest("instance has no upstream resource")
	}
	if instance.CloudServerID == nil {
		return upstreamCallResult{}, BadRequest("instance has no cloud server")
	}
	result, err := s.submitUpstreamPath(ctx, instance, path, method, payload)
	if err != nil {
		return upstreamCallResult{}, err
	}
	if result.StatusCode < 200 || result.StatusCode >= 300 {
		return upstreamCallResult{}, HTTPError{Status: 502, Detail: fmt.Sprintf("upstream returned HTTP %d: %s", result.StatusCode, trimText(result.Text, 200))}
	}
	return result, nil
}

func upstreamResultMap(result upstreamCallResult) map[string]any {
	payload := result.Payload
	if len(payload) == 0 {
		payload = result.Body
	}
	return payload
}

func newInstanceSummaryForSync(instance Instance) map[string]any {
	return map[string]any{
		"id":         instance.ID,
		"status":     instance.Status,
		"main_ip":    instance.MainIP,
		"hostname":   instance.Hostname,
		"ssh_port":   instance.SSHPort,
		"expires_at": instance.ExpiresAt,
		"synced_at":  time.Now().UTC(),
	}
}

func intFromAny(values ...any) *int {
	for _, value := range values {
		text := strings.TrimSpace(fmt.Sprint(value))
		if text == "" || text == "<nil>" {
			continue
		}
		var out int
		if _, err := fmt.Sscan(text, &out); err == nil {
			return &out
		}
	}
	return nil
}
