package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"reflect"
	"regexp/syntax"

	"github.com/ghodss/yaml"
	"github.com/tidbcloud/clusterproxy/route"
	"github.com/tidbcloud/clusterproxy/route/router"
	_ "github.com/tidbcloud/clusterproxy/service"
)

var (
	source = flag.String("source", "service.lock.yaml", "The generated YAML file of the registered services")
	output = flag.String("output", "", "The output path of the generated YAML file")
)

func main() {
	flag.Parse()
	bytes, err := ioutil.ReadFile(*source)
	if err != nil {
		log.Fatal(err)
	}
	var existed []*route.Route

	if err := yaml.Unmarshal(bytes, existed); err != nil {
		log.Fatal(err)
	}
	oldRoutes := parseRoutes(existed)
	newRoutes := parseRoutes(router.CloneRegisteredRoutes())

	checkNoDeletion(oldRoutes, newRoutes)
	checkNoSchemaChange(oldRoutes, newRoutes)
	checkNoDowngrade(oldRoutes, newRoutes)

	fmt.Println("Version check passed.")
	if *output == "" {
		return
	}
	data, err := yaml.Marshal(router.CloneRegisteredRoutes())
	if err != nil {
		log.Fatal(err)
	}
	if err := ioutil.WriteFile(*output, data, 0744); err != nil {
		log.Fatal(err)
	}
}

func checkNoDeletion(oldRoutes, newRoutes map[string]*route.Route) {
	for name := range oldRoutes {
		if newRoutes[name] == nil {
			log.Fatalf("The existing route %s has been deleted.", name)
		}
	}
}
func checkNoSchemaChange(oldRoutes, newRoutes map[string]*route.Route) {
	for name, newRoute := range newRoutes {
		oldRoute := oldRoutes[name]
		if oldRoute == nil {
			continue
		}
		if newRoute.Version != oldRoute.Version {
			continue
		}
		// Schema changes are allowed for non-final versions.
		if newRoute.Version != route.V1 {
			continue
		}
		if oldRoute.Path != newRoute.Path {
			log.Fatalf("The path of route %s has changed.", name)
		}
		if !reflect.DeepEqual(oldRoute.Schema.RequiredHeaders, newRoute.Schema.RequiredHeaders) {
			log.Fatalf("The required headers of route %s has changed.", name)
		}
		if !reflect.DeepEqual(oldRoute.Schema.JSONBodyModel, newRoute.Schema.JSONBodyModel) {
			log.Fatalf("The JSON body model of route %s has changed.", name)
		}

		oldRegex, err := syntax.Parse(oldRoute.Schema.PathRegex, syntax.OneLine)
		if err != nil {
			log.Fatal(err)
		}
		newRegex, err := syntax.Parse(oldRoute.Schema.PathRegex, syntax.OneLine)
		if err != nil {
			log.Fatal(err)
		}
		if !oldRegex.Equal(newRegex) {
			log.Fatalf("The subpath schema of route %s has changed.", name)
		}
	}
}

func checkNoDowngrade(oldRoutes, newRoutes map[string]*route.Route) {
	for name, oldRoute := range oldRoutes {
		newRoute := newRoutes[name]
		if oldRoute.Version.GreaterThan(newRoute.Version) {
			log.Fatalf("Route %s has been downgraded", name)
		}
	}
}

func parseRoutes(routes []*route.Route) map[string]*route.Route {
	scheme := make(map[string]*route.Route)
	for _, r := range routes {
		if scheme[r.Name] != nil {
			log.Fatalf("Duplicate route name %s", r.Name)
		}
		scheme[r.Name] = r
	}
	return scheme
}
