package model

import (
	"fmt"
	"io/ioutil"

	"github.com/ghodss/yaml"
	"github.com/tidbcloud/service-catalog/pkg/controller"
)

type Service struct {
	PulumiPath  string `json:"pulumi_path"`
	Repo        string `json:"repo"`
	ServiceName string `json:"service_name"`
	Branch      string `json:"branch"`
	Application string `json:"application"`
	Business    string `json:"business"`
}

type RepoRPC struct {
	ProtoPath string `json:"proto_path"`
	Repo      string `json:"repo"`
	RPCs      []*RPC `json:"rpcs"`
}

type RPC struct {
	GrpcService    string                 `json:"service"`
	GrpcMethods    []string               `json:"methods"`
	RequestMethods []RPCMethodWithRequest `json:"request_methods"`
}

type RPCMethodWithRequest struct {
	Method        string `json:"method"`
	MaxQPS        string `json:"max_qps"`
	RequestsCount string `json:"requests_count"`
}

type API struct {
	Path          string `json:"path"`
	Method        string `json:"method"`
	MaxQPS        string `json:"max_qps"`
	RequestsCount string `json:"requests_count"`
}

type RepoHTTP struct {
	SwaggerPath string `json:"swagger_path"`
	Repo        string `json:"repo"`
	APIs        []*API `json:"apis"`
}

type ManualRepo struct {
	Business    string `json:"business"`
	Application string `json:"application"`
}

type ManualService struct {
	ProtoPaths    []string `json:"proto_paths"`
	SwaggerPaths  []string `json:"swagger_paths"`
	KubeService   string   `json:"kube_service"`
	JaegerService string   `json:"jaeger_service"`
}

type ManualApplicationCdType string

const (
	CdTypePulumi ManualApplicationCdType = "pulumi"
	CdTypeArgo   ManualApplicationCdType = "argo"
)

type ManualApplication struct {
	Name          string                   `json:"name"`
	Owners        []string                 `json:"owners"`
	CdType        ManualApplicationCdType  `json:"cdType"`
	WindowStartAt *ManualApplicationWindow `json:"windowStartAt"`
	WindowEndAt   *ManualApplicationWindow `json:"windowEndAt"`
	Slis          []*ManualApplicationSli  `json:"slis"`
}

type ManualApplicationWindow struct {
	Weekday int `json:"weekday"`
	Hour    int `json:"hour"`
	Minute  int `json:"minute"`
}

type ManualApplicationSliType string

const (
	SliTypeAvailability ManualApplicationSliType = "Availability"
	SliTypeLatency      ManualApplicationSliType = "Latency"
)

type ManualApplicationSli struct {
	Type       ManualApplicationSliType         `json:"type"`
	Name       string                           `json:"name"`
	Objectives []*ManualApplicationSliObjective `json:"objectives"`
}

type ManualApplicationSliObjectiveOperator string

const (
	OpGt  ManualApplicationSliObjectiveOperator = "gt"
	OpLt  ManualApplicationSliObjectiveOperator = "lt"
	OpGte ManualApplicationSliObjectiveOperator = "gte"
	OpLte ManualApplicationSliObjectiveOperator = "lte"
)

type ManualApplicationSliObjective struct {
	Name   string                                `json:"name"`
	PromQL string                                `json:"promQL"`
	Op     ManualApplicationSliObjectiveOperator `json:"op"`
	Value  float64                               `json:"value"`
}

type Manual struct {
	Repos        map[string]*ManualRepo        `json:"repos"`
	Services     map[string]*ManualService     `json:"services"`
	Applications map[string]*ManualApplication `json:"applications"`
}

type Meta struct {
	PulumiPath     string                     `json:"pulumi_path"`
	Repo           string                     `json:"repo"`
	ServiceName    string                     `json:"service_name"`
	Branch         string                     `json:"branch"`
	Application    string                     `json:"application"`
	Business       string                     `json:"business"`
	RepoRPCs       []*RepoRPC                 `json:"repo_rpc"`
	RepoHTTPs      []*RepoHTTP                `json:"repo_http"`
	KubeService    string                     `json:"kube_service"`
	JaegerService  string                     `json:"jaeger_service"`
	KubeDeployment *controller.KubeDeployment `json:"kube_deployment,omitempty"`
}

func ReadMetas(directory string) (map[string]*Meta, map[string]*ManualApplication, error) {
	services, err := readServices(directory + "/services.yaml")
	if err != nil {
		return nil, nil, err
	}
	proto2RepoRPC, err := readRepoRPCs(directory + "/rpcs.yaml")
	if err != nil {
		return nil, nil, err
	}
	swagger2RepoHTTP, err := readRepoHTTPs(directory + "/swagger_https.yaml")
	if err != nil {
		return nil, nil, err
	}
	manual, err := readManualMeta(directory + "/manual_meta.yaml")
	if err != nil {
		return nil, nil, err
	}
	result := make(map[string]*Meta)
	for _, service := range services {
		meta := Meta{
			PulumiPath:  service.PulumiPath,
			Repo:        service.Repo,
			ServiceName: service.ServiceName,
			Branch:      service.Branch,
			Application: service.Application,
			Business:    service.Business,
			RepoRPCs:    make([]*RepoRPC, 0),
			RepoHTTPs:   make([]*RepoHTTP, 0),
		}

		if service.Repo != "" {
			manualRepo, ok := manual.Repos[service.Repo]
			if !ok {
				return nil, nil, fmt.Errorf("%s does not have valid Biz & App", service.ServiceName)
			}
			meta.Application = manualRepo.Application
			meta.Business = manualRepo.Business
		}

		manualService, ok := manual.Services[service.ServiceName]
		if ok {
			for _, protoPath := range manualService.ProtoPaths {
				repoRPC, ok := proto2RepoRPC[service.Repo+protoPath]
				if !ok {
					return nil, nil, fmt.Errorf("%s got invalid proto path %s", service.ServiceName, protoPath)
				}
				meta.RepoRPCs = append(meta.RepoRPCs, repoRPC)
			}
			for _, swaggerPath := range manualService.SwaggerPaths {
				repoHTTP, ok := swagger2RepoHTTP[service.Repo+swaggerPath]
				if !ok {
					return nil, nil, fmt.Errorf("%s go invalid swagger path %s", service.ServiceName, swaggerPath)
				}
				meta.RepoHTTPs = append(meta.RepoHTTPs, repoHTTP)
			}
			meta.KubeService = manualService.KubeService
			meta.JaegerService = manualService.JaegerService
		}

		result[service.ServiceName] = &meta
	}
	return result, manual.Applications, nil
}

func readServices(path string) ([]*Service, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var services []*Service
	err = yaml.Unmarshal(content, &services)
	if err != nil {
		return nil, err
	}
	return services, nil
}

func readRepoRPCs(path string) (map[string]*RepoRPC, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var rpcs []*RepoRPC
	err = yaml.Unmarshal(content, &rpcs)
	if err != nil {
		return nil, err
	}
	result := make(map[string]*RepoRPC)
	for _, r := range rpcs {
		result[r.Repo+r.ProtoPath] = r
	}
	return result, nil
}

func readRepoHTTPs(path string) (map[string]*RepoHTTP, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var https []*RepoHTTP
	err = yaml.Unmarshal(content, &https)
	if err != nil {
		return nil, err
	}
	result := make(map[string]*RepoHTTP)
	for _, h := range https {
		result[h.Repo+h.SwaggerPath] = h
	}
	return result, nil
}

func readManualMeta(path string) (*Manual, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var manualMeta Manual
	err = yaml.Unmarshal(content, &manualMeta)
	if err != nil {
		return nil, err
	}

	// XXX(bing.wang) fill application name to object
	for name, _ := range manualMeta.Applications {
		manualMeta.Applications[name].Name = name
	}

	return &manualMeta, err
}
