package dto

import (
	"fmt"
	"strings"
	"time"

	"slices"

	"github.com/tidbcloud/pov-service/internal/common/params"
	"github.com/tidbcloud/pov-service/internal/config"
	"github.com/tidbcloud/pov-service/internal/model"
)

type ListWorkloadReq struct {
	ListReq

	Type     model.WorkloadType `json:"type"  query:"type"`
	Keyword  string             `json:"keyword"  query:"keyword"`
	Industry string             `json:"industry"  query:"industry"`
}

func (req ListWorkloadReq) Validate() error {
	// 1. Validate @Type
	// Assuming model.WorkloadType has an IsValid() method defined in the model package.
	// If req.Type is empty, it means the type filter is not applied, which is valid.
	if req.Type != "" && !req.Type.IsValid() {
		return fmt.Errorf("invalid workload type: %q", req.Type)
	}

	// 2. Validate @ListReq.OrderBy
	// Check if OrderBy field is provided (not empty).
	if req.OrderBy != "" {
		sortableFields := WorkloadResp{}.SortableFields() // Get the list of allowed sortable fields
		found := slices.Contains(sortableFields, req.OrderBy)
		if !found {
			return fmt.Errorf("invalid OrderBy field: %q. Sortable fields are: [%s]", req.OrderBy, strings.Join(sortableFields, ", "))
		}
	}

	return nil
}

type WorkloadResp struct {
	model.Workload
	TestCount int `json:"testCount"`
}

// demofuncs
func (w WorkloadResp) SortableFields() []string {
	return []string{
		"name",
		"description",
		"type"}
}

func NewWorkloadResps(wls []model.Workload, useCount map[int64]int) []WorkloadResp {
	resps := make([]WorkloadResp, len(wls))
	for i, wl := range wls {
		resps[i] = WorkloadResp{Workload: wl}
		if cnt, ok := useCount[wl.ID]; ok {
			resps[i].TestCount = cnt
		}
	}
	return resps
}

type ListWorkloadResp struct {
	Workloads []WorkloadResp `json:"workloads"`
	PageResp
}

type WorkloadTestResp struct {
	model.WorkloadTest
}

func NewWorkloadTestResps(wts []model.WorkloadTest) []WorkloadTestResp {
	resps := make([]WorkloadTestResp, len(wts))
	for i, wt := range wts {
		resps[i] = WorkloadTestResp{WorkloadTest: wt}
	}
	return resps
}

type ListWorkloadTestResp struct {
	Tests []WorkloadTestResp `json:"tests"`
	PageResp
}

type RunWorkloadReq struct {
	params.RunWorkload
}

type RunWorkloadResp struct {
	model.WorkloadTest
}

type ListWorkloadTestsReq struct {
	ListReq

	Status model.ExecStatus `json:"status"  query:"status"`
}

type ScaleClusterReq struct {
	Replicas int `json:"replicas"`
}

type UpdateWorkloadTestMaxConnReq struct {
	MaxConn int `json:"maxConnectionNumber"`
}

func (req UpdateWorkloadTestMaxConnReq) Validate() error {
	if req.MaxConn <= 0 {
		return fmt.Errorf("maxConnectionNumber must be greater than 0")
	}
	return nil
}

type UpdateClusterRUReq struct {
	MinRU int64 `json:"minRU"`
	MaxRU int64 `json:"maxRU"`
}

func (req UpdateClusterRUReq) Validate() error {
	if req.MinRU <= 0 || req.MaxRU <= 0 {
		return fmt.Errorf("minRU and maxRU must be greater than 0")
	}
	if req.MinRU > req.MaxRU {
		return fmt.Errorf("minRU cannot be greater than maxRU")
	}
	return nil
}

type InjectFailureReq struct {
	Duration string `json:"duration"`
}

func (req InjectFailureReq) Validate() error {
	if req.Duration == "" {
		return fmt.Errorf("duration is required")
	}
	// Validate duration format by attempting to parse it
	if _, err := time.ParseDuration(req.Duration); err != nil {
		return fmt.Errorf("invalid duration format: %s", req.Duration)
	}
	return nil
}

// NOTE: All fields are declative
// which means the original values should be sent in the request too.
type UpdateWorkloadTestReq struct {
	UpdateWorkloadTestMaxConnReq
	UpdateClusterRUReq
}

func (r UpdateWorkloadTestReq) Validate() error {
	if err := r.UpdateWorkloadTestMaxConnReq.Validate(); err != nil {
		return err
	}
	if err := r.UpdateClusterRUReq.Validate(); err != nil {
		return err
	}
	return nil
}

func (r UpdateWorkloadTestReq) IsMaxConnUpdated() bool {
	return r.MaxConn != 0
}

func (r UpdateWorkloadTestReq) IsRUUpdated() bool {
	return r.MinRU != 0 || r.MaxRU != 0
}

type SampleDBReq struct {
	model.SampleDBOptions
}

type SampleDBResp struct {
	model.DBSampleResult
	// PageResp
}

type SampleDataReq struct {
	model.SampleDataOptions
}

type SampleDataResp struct {
	model.TableQueryResult
	// PageResp
}

type ListAuditLogResp struct {
	AuditLogs []model.AuditLog `json:"auditLogs"`
	PageResp
}

type DownloadTestReportReq struct {
	Width             float64 `json:"width"`
	Height            float64 `json:"height"`
	Address           string  `json:"address"`
	ReadySelector     string  `json:"readySelector"`
	LocalStoragesJSON string  `json:"localStoragesJson"`
}

func (r DownloadTestReportReq) Validate() error {
	return nil
}

func (r DownloadTestReportReq) GetAddress(testID string) string {
	if r.Address != "" {
		return r.Address
	}

	return fmt.Sprintf("https://%s/pov/workload/%s", config.PoVServiceFEDomain(), testID)
}

func (r DownloadTestReportReq) GetReadySelector() string {
	if r.ReadySelector != "" {
		return r.ReadySelector
	}
	return "//*[normalize-space()='Configuration']"
}

type ScriptResp struct {
	model.Script
	WorkloadName string            `json:"workloadName,omitempty"`
	Phases       []ScriptPhaseResp `json:"phases,omitempty"`
}

func NewScriptResps(
	sts []model.Script) []ScriptResp {
	resps := make([]ScriptResp, len(sts))
	for i, st := range sts {
		resps[i] = ScriptResp{
			Script: st,
			Phases: NewScriptPhasesResps(st.Phases),
		}
	}
	return resps
}

type ScriptPhaseResp struct {
	Name          string                 `json:"name"`
	State         string                 `json:"state"`
	Description   string                 `json:"description"`
	Message       string                 `json:"message,omitempty"`
	StartDuration time.Duration          `json:"startDuration,omitempty" swaggertype:"integer" format:"int64"`
	Mutations     []model.ScriptMutation `json:"mutations,omitempty"`

	TypeDetail model.ScriptTypeDetail `json:"typeDetail"`
}

type ScriptTemplateResp struct {
	model.ScriptTemplate
	Phases []ScriptPhaseResp `json:"phases,omitempty"`
}

func NewScriptPhasesResps(phases []model.ScriptPhase) []ScriptPhaseResp {
	resps := make([]ScriptPhaseResp, len(phases))
	for i, phase := range phases {
		resps[i] = ScriptPhaseResp{
			Name:          phase.GetName(),
			State:         phase.GetState(),
			Description:   phase.GetDescription(),
			Message:       phase.GetMessage(),
			StartDuration: phase.GetStartDuration(),
			Mutations:     phase.GetMutations(),
			TypeDetail:    phase.GetTypeDetail(),
		}
	}
	return resps
}

func NewScriptTemplateResps(
	sts []model.ScriptTemplate) []ScriptTemplateResp {
	resps := make([]ScriptTemplateResp, len(sts))
	for i, st := range sts {
		resps[i] = ScriptTemplateResp{
			ScriptTemplate: st,
			Phases:         NewScriptPhasesResps(st.Phases),
		}
	}
	return resps
}

type ListScriptTemplatesResp struct {
	Templates []ScriptTemplateResp `json:"templates"`
	PageResp
}

type ListScriptReq struct {
	ListReq

	TestID string `json:"testID" query:"testID"`
}

type ListScriptResp struct {
	Templates []ScriptResp `json:"scripts"`
	PageResp
}

type ScriptPhaseReq struct {
	ID            int64                  `json:"id"`
	Type          string                 `json:"type"`
	Name          string                 `json:"name"`
	State         string                 `json:"state"`
	Description   string                 `json:"description"`
	Message       string                 `json:"message"`
	StartDuration FriendlyDuration       `json:"startDuration"`
	Mutations     []model.ScriptMutation `json:"mutations"`
}

func (r ScriptPhaseReq) toModel() (model.ScriptPhase, error) {
	switch strings.ToLower(r.Type) {
	case "mutate":
		phase := &model.ScriptMutatePhase{
			Name:          r.Name,
			ID:            r.ID,
			Description:   r.Description,
			Message:       r.Message,
			State:         r.State,
			Mutations:     append([]model.ScriptMutation(nil), r.Mutations...),
			StartDuration: r.StartDuration.Duration(),
		}
		return phase, nil
	case "proceed":
		return &model.ScriptProceedPhase{
			Name:        r.Name,
			ID:          r.ID,
			Description: r.Description,
			Message:     r.Message,
			State:       r.State,
		}, nil
	default:
		return nil, fmt.Errorf("unsupported script phase type: %s", r.Type)
	}
}

type ScriptTemplateUpsertReq struct {
	Name          string           `json:"name"`
	Description   string           `json:"description"`
	TotalDuration FriendlyDuration `json:"totalDuration"`
	Phases        []ScriptPhaseReq `json:"phases"`
	CreatorEmail  string           `json:"creatorEmail"`
	Version       int64            `json:"version"`
}

func (r ScriptTemplateUpsertReq) ToModel() (model.ScriptTemplate, error) {
	if strings.TrimSpace(r.Name) == "" {
		return model.ScriptTemplate{}, fmt.Errorf("name is required")
	}

	phases := make([]model.ScriptPhase, 0, len(r.Phases))
	for _, phaseReq := range r.Phases {
		phase, err := phaseReq.toModel()
		if err != nil {
			return model.ScriptTemplate{}, err
		}
		phases = append(phases, phase)
	}

	return model.ScriptTemplate{
		Version:      r.Version,
		CreatorEmail: r.CreatorEmail,
		ScriptSpec: model.ScriptSpec{
			Name:          r.Name,
			Description:   r.Description,
			TotalDuration: r.TotalDuration.Duration(),
			Phases:        phases,
		},
	}, nil
}

type ScriptUpsertReq struct {
	TemplateID    int64            `json:"templateId"`
	WorkloadID    int64            `json:"workloadID"`
	ClusterType   string           `json:"clusterType"`
	Name          string           `json:"name"`
	Description   string           `json:"description"`
	TotalDuration FriendlyDuration `json:"totalDuration"`
	Phases        []ScriptPhaseReq `json:"phases"`
	CreatorEmail  string           `json:"creatorEmail"`
}

func (r ScriptUpsertReq) ToModel() (model.Script, error) {
	if r.TemplateID <= 0 {
		return model.Script{}, fmt.Errorf("templateId is required")
	}
	if strings.TrimSpace(r.Name) == "" {
		return model.Script{}, fmt.Errorf("name is required")
	}

	phases := make([]model.ScriptPhase, 0, len(r.Phases))
	for _, phaseReq := range r.Phases {
		phase, err := phaseReq.toModel()
		if err != nil {
			return model.Script{}, err
		}
		phases = append(phases, phase)
	}

	return model.Script{
		TemplateID:   r.TemplateID,
		CreatorEmail: r.CreatorEmail,
		ScriptSelector: model.ScriptSelector{
			WorkloadID:  r.WorkloadID,
			ClusterType: model.ClusterType(strings.ToLower(r.ClusterType)),
		},
		ScriptSpec: model.ScriptSpec{
			Name:          r.Name,
			Description:   r.Description,
			TotalDuration: r.TotalDuration.Duration(),
			Phases:        phases,
		},
	}, nil
}
