package model

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"sort"
	"time"

	validation "github.com/Thearas/ozzo-validation/v4"
)

type RMS struct {
	ID        ID        `json:"id" db:"id"`
	Name      string    `json:"name" db:"name"`
	Endpoint  string    `json:"endpoint" db:"endpoint"`
	Token     string    `json:"token" db:"token"`
	CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
	UpdatedAt time.Time `json:"updatedAt,omitempty" db:"updated_at"`
}

type ResourcePool struct {
	ID        ID         `json:"id"`
	RmsURL    string     `json:"rmsURL"`
	RmsName   string     `json:"rmsName"`
	Name      string     `json:"name"`
	Aliases   string     `json:"aliases"`
	CreatedAt *time.Time `json:"createdAt,omitempty"`
	UpdatedAt *time.Time `json:"updatedAt,omitempty"`

	TotalResource              PhysicalResource `json:"totalResource,omitempty"`
	NonTestbedOccupiedResource PhysicalResource `json:"nonTestbedOccupationResource,omitempty"`
	IdleResource               PhysicalResource `json:"idleResource,omitempty"`

	BorrowedPolicy ResourcePoolBorrowedPolicies `json:"borrowedPolicy,omitempty"`
}

type PhysicalResource struct {
	CPU    CPUResource    `json:"cpu,omitempty"`
	Memory MemoryResource `json:"memory,omitempty"`
}

type CPUResource struct {
	Core float64 `json:"core,omitempty"`
}

type MemoryResource struct {
	Byte int `json:"byte,omitempty"`
}

type ResourcePoolBorrowedPolicies []ResourcePoolBorrowedPolicy

func (vs ResourcePoolBorrowedPolicies) Validate() error {
	sort.Slice(vs, func(i, j int) bool {
		return vs[i].TimeStart < vs[j].TimeStart
	})

	for i, v := range vs {
		if err := v.Validate(); err != nil {
			return err
		}
		if i == 0 {
			continue
		}
		end, _ := vs[i-1].TimeEnd.Time()
		if v.TimeStart.After(end) {
			return fmt.Errorf("timeStart '%s' must be before timeEnd '%s'", v.TimeStart, v.TimeEnd)
		}
	}
	return nil
}

func (s ResourcePoolBorrowedPolicies) Value() (driver.Value, error) {
	if len(s) == 0 {
		return []byte("[]"), nil
	}
	return json.Marshal(s)
}

func (s *ResourcePoolBorrowedPolicies) Scan(src interface{}) error {
	var data []byte
	switch v := src.(type) {
	case string:
		data = []byte(v)
	case []byte:
		data = v
	default:
	}
	if len(data) == 0 {
		*s = []ResourcePoolBorrowedPolicy{}
		return nil
	}
	return json.Unmarshal(data, s)
}

// EffectivePolicy returns the effective policy.
func (v ResourcePoolBorrowedPolicies) EffectivePolicy(borrowedCPUPercentage float32) *ResourcePoolBorrowedPolicy {
	now := time.Now()
	for _, policy := range v {
		if policy.IsEffective(now, borrowedCPUPercentage) {
			return &policy
		}
	}
	return nil
}

type ResourcePoolBorrowedPolicy struct {
	// Format: '01:00'.
	TimeStart HourMinute `json:"timeStart"`
	TimeEnd   HourMinute `json:"timeEnd"`

	// Percentage of max borrowed resources.
	Capacity float32 `json:"capacity"`
}

func (v ResourcePoolBorrowedPolicy) Validate() error {
	start, err := v.TimeStart.Time()
	if err != nil {
		return err
	}
	end, err := v.TimeEnd.Time()
	if err != nil {
		return err
	}
	if !end.After(start) {
		return fmt.Errorf("timeStart '%s' must be before timeEnd '%s'", v.TimeStart, v.TimeEnd)
	}

	return validation.ValidateStruct(&v,
		validation.Field(&v.Capacity, validation.Max(0.99)),
	)
}

func (v *ResourcePoolBorrowedPolicy) IsEffective(ts time.Time, borrowedCPUPercentage float32) bool {
	matchTimeRange := v.TimeStart.BeforeOrEqual(ts) && v.TimeEnd.After(ts)
	matchResourceCapacity := v.Capacity > borrowedCPUPercentage
	return matchTimeRange && matchResourceCapacity
}
