package model

import (
	"context"
	"strings"

	"github.com/spf13/viper"
	"github.com/tidbcloud/service-catalog/pkg/controller"
	"github.com/tidbcloud/service-catalog/pkg/prom"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const (
	KindRoot string = "root"
	KindBiz         = "business"
	KindApp         = "application"
	KindSvc         = "service"
)

type Node struct {
	Kind           string                     `json:"kind"`
	Name           string                     `json:"name"`
	Children       []*Node                    `json:"children"`
	KubeDeployment *controller.KubeDeployment `json:"kube_deployment,omitempty"`
}

func (n *Node) getOrCreateChild(kind, name string) *Node {
	for _, child := range n.Children {
		if name == child.Name {
			return child
		}
	}
	node := &Node{Kind: kind, Name: name}
	n.Children = append(n.Children, node)
	return node
}

func findKubeDeployment(serviceName string, kubeDeployments []*controller.KubeDeployment) *controller.KubeDeployment {
	for _, k := range kubeDeployments {
		if strings.HasPrefix(k.Name, serviceName) {
			return k
		}
	}
	return nil
}

type MetaProvider struct {
	metas        map[string]*Meta
	applications map[string]*ManualApplication
}

func NewMetaProvider(directory string) (*MetaProvider, error) {
	metas, applications, err := ReadMetas(directory)
	if err != nil {
		return nil, err
	}
	kubeDeployments, err := controller.ListDeployments(viper.GetString("serviceNamespace"))
	if err != nil {
		return nil, err
	}
	metas = PatchMeta(metas, kubeDeployments)
	return &MetaProvider{
		metas:        metas,
		applications: applications,
	}, nil
}

func PatchMeta(metas map[string]*Meta, kubeDeployments []*controller.KubeDeployment) map[string]*Meta {
	matchedDeployments := make(map[string]bool)
	for _, meta := range metas {
		kubeDeployment := findKubeDeployment(meta.ServiceName, kubeDeployments)
		if kubeDeployment != nil {
			meta.KubeDeployment = kubeDeployment
			matchedDeployments[kubeDeployment.Name] = true // Mark as matched
		}
	}

	for _, kubeDeployment := range kubeDeployments {
		_, exists := matchedDeployments[kubeDeployment.Name]
		if !exists {
			metas[kubeDeployment.Name] = &Meta{
				PulumiPath:     "",
				Repo:           "",
				ServiceName:    kubeDeployment.Name,
				Branch:         "",
				Business:       "Uncategorized",
				Application:    kubeDeployment.NameSpace,
				RepoRPCs:       make([]*RepoRPC, 0),
				RepoHTTPs:      make([]*RepoHTTP, 0),
				KubeService:    kubeDeployment.Name,
				JaegerService:  kubeDeployment.Name,
				KubeDeployment: kubeDeployment,
			}
		}
	}
	return metas
}

func (mp *MetaProvider) BuildTree() (*Node, error) {
	root := &Node{Kind: KindRoot, Name: "TiDB Cloud"}
	kubeDeployments, err := controller.ListDeployments(viper.GetString("serviceNamespace"))
	if err != nil {
		return nil, err
	}
	// Create a map to track matched KubeDeployments
	matchedDeployments := make(map[string]bool)
	for _, meta := range mp.metas {
		biz := root.getOrCreateChild(KindBiz, meta.Business)
		app := biz.getOrCreateChild(KindApp, meta.Application)
		svc := app.getOrCreateChild(KindSvc, meta.ServiceName)
		kubeDeployment := findKubeDeployment(meta.ServiceName, kubeDeployments)
		if kubeDeployment != nil {
			svc.KubeDeployment = kubeDeployment
			matchedDeployments[kubeDeployment.Name] = true // Mark as matched
		}
	}
	defaultBiz := root.getOrCreateChild(KindBiz, "Uncategorized")
	// Add unmatched KubeDeployments to the default node
	for _, kubeDeployment := range kubeDeployments {
		if !matchedDeployments[kubeDeployment.Name] {
			app := defaultBiz.getOrCreateChild(KindApp, kubeDeployment.NameSpace)
			svc := app.getOrCreateChild(KindSvc, kubeDeployment.Name)
			svc.KubeDeployment = kubeDeployment
		}
	}

	return root, nil
}

func (mp *MetaProvider) GetMeta(ctx context.Context, serviceName string) (*Meta, error) {
	meta, ok := mp.metas[serviceName]
	if !ok {
		return nil, nil
	}

	for _, rr := range meta.RepoRPCs {
		for _, rpc := range rr.RPCs {
			rm := make([]RPCMethodWithRequest, 0, 10)
			for _, method := range rpc.GrpcMethods {
				samples, err := prom.GetGrpcSamples(rpc.GrpcService, method)
				if err != nil {
					tilog.Warn(ctx, "failed to get grpc samples", zap.Error(err))
					continue
				}
				rm = append(rm, RPCMethodWithRequest{
					Method:        method,
					MaxQPS:        samples.MaxQPS(),
					RequestsCount: samples.RequestsCount(),
				})
			}
			rpc.RequestMethods = rm
		}
	}

	for _, rh := range meta.RepoHTTPs {
		for _, api := range rh.APIs {
			samples, err := prom.GetHttpSamples(api.Path)
			if err != nil {
				tilog.Warn(ctx, "failed to get http samples", zap.Error(err))
				continue
			}
			api.MaxQPS = samples.MaxQPS()
			api.RequestsCount = samples.RequestsCount()
		}
	}

	kubeDeployments, err := controller.ListDeployments(viper.GetString("serviceNamespace"))
	if err != nil {
		return nil, err
	}
	kubeDeployment := findKubeDeployment(meta.ServiceName, kubeDeployments)
	if kubeDeployment != nil {
		meta.KubeDeployment = kubeDeployment
	}
	return meta, nil
}

func (mp *MetaProvider) ListApplications(ctx context.Context) ([]*ManualApplication, error) {
	var apps []*ManualApplication
	for _, app := range mp.applications {
		apps = append(apps, app)
	}
	return apps, nil
}

func (mp *MetaProvider) GetApplication(ctx context.Context, applicationName string) (*ManualApplication, error) {
	application := mp.applications[applicationName]
	return application, nil
}
