package codegen

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"strconv"
	"strings"

	"github.com/samber/lo"
)

const (
	typescriptEndpointsFileName     = "endpoints.ts"
	typescriptEndpointTypesFileName = "endpoint.types.ts"
	typescriptHooksFileName         = "hooks_template.ts"
	goEndpointsFileName             = "endpoints.go"
	space                           = "    "
)

var additionalStructNamesForEndpoints = []string{}

func GenerateTypescriptEndpointsFile(handlersJsonPath string, structsJsonPath string, outDir string, eventDir string) []string {
	handlers := LoadHandlers(handlersJsonPath)
	structs := LoadPublicStructs(structsJsonPath)

	_ = os.MkdirAll(outDir, os.ModePerm)
	f, err := os.Create(filepath.Join(outDir, typescriptEndpointsFileName))
	if err != nil {
		panic(err)
	}
	defer f.Close()

	typeF, err := os.Create(filepath.Join(outDir, typescriptEndpointTypesFileName))
	if err != nil {
		panic(err)
	}
	defer typeF.Close()

	hooksF, err := os.Create(filepath.Join(outDir, typescriptHooksFileName))
	if err != nil {
		panic(err)
	}
	defer hooksF.Close()

	f.WriteString("// This code was generated by codegen/main.go. DO NOT EDIT.\n\n")

	f.WriteString(`export type ApiEndpoints = Record<string, Record<string, {
     key: string,
     methods: ("POST" | "GET" | "PATCH" | "PUT" | "DELETE")[],
     endpoint: string
}>>

`)

	f.WriteString("export const API_ENDPOINTS = {\n")

	groupedByFile := make(map[string][]*RouteHandler)
	for _, handler := range handlers {
		if _, ok := groupedByFile[handler.Filename]; !ok {
			groupedByFile[handler.Filename] = make([]*RouteHandler, 0)
		}
		groupedByFile[handler.Filename] = append(groupedByFile[handler.Filename], handler)
	}

	filenames := make([]string, 0)
	for k := range groupedByFile {
		filenames = append(filenames, k)
	}

	slices.SortStableFunc(filenames, func(i, j string) int {
		return strings.Compare(i, j)
	})

	// Store the endpoints
	endpointsMap := make(map[string]string)

	for _, filename := range filenames {
		routes := groupedByFile[filename]
		if len(routes) == 0 {
			continue
		}

		if lo.EveryBy(routes, func(route *RouteHandler) bool {
			return route.Api == nil || len(route.Api.Methods) == 0
		}) {
			continue
		}

		groupName := strings.ToUpper(strings.TrimSuffix(filename, ".go"))

		writeLine(f, fmt.Sprintf("\t%s: {", groupName)) // USERS: {

		for _, route := range groupedByFile[filename] {
			if route.Api == nil || len(route.Api.Methods) == 0 {
				continue
			}

			if len(route.Api.Descriptions) > 0 {
				writeLine(f, "        /**")
				f.WriteString(fmt.Sprintf("         *  @description\n"))
				f.WriteString(fmt.Sprintf("         *  Route %s\n", route.Api.Summary))
				for _, cmt := range route.Api.Descriptions {
					writeLine(f, fmt.Sprintf("         *  %s", strings.TrimSpace(cmt)))
				}
				writeLine(f, "         */")
			}

			writeLine(f, fmt.Sprintf("\t\t%s: {", strings.TrimPrefix(route.Name, "Handle"))) // GetAnimeCollection: {

			methodStr := ""
			if len(route.Api.Methods) > 1 {
				methodStr = fmt.Sprintf("\"%s\"", strings.Join(route.Api.Methods, "\", \""))
			} else {
				methodStr = fmt.Sprintf("\"%s\"", route.Api.Methods[0])
			}

			endpointsMap[strings.TrimPrefix(route.Name, "Handle")] = getEndpointKey(route.Name, groupName)

			writeLine(f, fmt.Sprintf("\t\t\tkey: \"%s\",", getEndpointKey(route.Name, groupName)))

			writeLine(f, fmt.Sprintf("\t\t\tmethods: [%s],", methodStr)) // methods: ['GET'],

			writeLine(f, fmt.Sprintf("\t\t\tendpoint: \"%s\",", route.Api.Endpoint)) // path: '/api/v1/anilist/collection',

			writeLine(f, "\t\t},") // },
		}

		writeLine(f, "\t},") // },
	}

	f.WriteString("} satisfies ApiEndpoints\n\n")

	referenceGoStructs := make([]string, 0)
	for _, filename := range filenames {
		routes := groupedByFile[filename]
		if len(routes) == 0 {
			continue
		}
		for _, route := range groupedByFile[filename] {
			if route.Api == nil || len(route.Api.Methods) == 0 {
				continue
			}
			if len(route.Api.Params) == 0 && len(route.Api.BodyFields) == 0 {
				continue
			}
			for _, param := range route.Api.BodyFields {
				if param.UsedStructType != "" {
					referenceGoStructs = append(referenceGoStructs, param.UsedStructType)
				}
			}
			for _, param := range route.Api.Params {
				if param.UsedStructType != "" {
					referenceGoStructs = append(referenceGoStructs, param.UsedStructType)
				}
			}
		}
	}
	referenceGoStructs = lo.Uniq(referenceGoStructs)

	typeF.WriteString("// This code was generated by codegen/main.go. DO NOT EDIT.\n\n")

	//
	// Imports
	//
	importedTypes := make([]string, 0)
	//
	for _, structName := range referenceGoStructs {
		parts := strings.Split(structName, ".")
		if len(parts) != 2 {
			continue
		}

		var goStruct *GoStruct
		for _, s := range structs {
			if s.Name == parts[1] && s.Package == parts[0] {
				goStruct = s
				break
			}
		}

		if goStruct == nil {
			continue
		}

		importedTypes = append(importedTypes, goStruct.FormattedName)
	}

	for _, otherStrctName := range additionalStructNamesForEndpoints {
		importedTypes = append(importedTypes, stringGoTypeToTypescriptType(otherStrctName))
	}
	//
	slices.SortStableFunc(importedTypes, func(i, j string) int {
		return strings.Compare(i, j)
	})
	typeF.WriteString("import type {\n")
	for _, typeName := range importedTypes {
		typeF.WriteString(fmt.Sprintf("    %s,\n", typeName))
	}
	typeF.WriteString("} from \"@/api/generated/types.ts\"\n\n")

	//
	// Types
	//

	for _, filename := range filenames {
		routes := groupedByFile[filename]
		if len(routes) == 0 {
			continue
		}

		typeF.WriteString("//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n")
		typeF.WriteString(fmt.Sprintf("// %s\n", strings.TrimSuffix(filename, ".go")))
		typeF.WriteString("//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n")

		for _, route := range groupedByFile[filename] {
			if route.Api == nil || len(route.Api.Methods) == 0 {
				continue
			}

			if len(route.Api.Params) == 0 && len(route.Api.BodyFields) == 0 {
				continue
			}

			typeF.WriteString("/**\n")
			typeF.WriteString(fmt.Sprintf(" * - Filepath: %s\n", filepath.ToSlash(strings.TrimPrefix(route.Filepath, "..\\"))))
			typeF.WriteString(fmt.Sprintf(" * - Filename: %s\n", route.Filename))
			typeF.WriteString(fmt.Sprintf(" * - Endpoint: %s\n", route.Api.Endpoint))
			if len(route.Api.Summary) > 0 {
				typeF.WriteString(fmt.Sprintf(" * @description\n"))
				typeF.WriteString(fmt.Sprintf(" * Route %s\n", strings.TrimSpace(route.Api.Summary)))
			}
			typeF.WriteString(" */\n")
			typeF.WriteString(fmt.Sprintf("export type %s_Variables = {\n", strings.TrimPrefix(route.Name, "Handle"))) // export type EditAnimeEntry_Variables = {

			addedBodyFields := false
			for _, param := range route.Api.BodyFields {
				writeParamField(typeF, route, param) // mediaId: number;
				if param.UsedStructType != "" {
					referenceGoStructs = append(referenceGoStructs, param.UsedStructType)
				}
				addedBodyFields = true
			}

			if !addedBodyFields {
				for _, param := range route.Api.Params {
					writeParamField(typeF, route, param) // mediaId: number;
					if param.UsedStructType != "" {
						referenceGoStructs = append(referenceGoStructs, param.UsedStructType)
					}
				}
			}

			writeLine(typeF, "}\n")
		}

	}

	generateHooksFile(hooksF, groupedByFile, filenames)

	generateEventFile(eventDir, endpointsMap)

	return referenceGoStructs
}

func generateHooksFile(f *os.File, groupedHandlers map[string][]*RouteHandler, filenames []string) {

	queryTemplate := `// export function use{handlerName}({props}) {
//     return useServerQuery{<}{TData}{TVar}{>}({
//         endpoint: API_ENDPOINTS.{groupName}.{handlerName}.endpoint{endpointSuffix},
//         method: API_ENDPOINTS.{groupName}.{handlerName}.methods[%d],
//         queryKey: [API_ENDPOINTS.{groupName}.{handlerName}.key],
//         enabled: true,
//     })
// }

`
	mutationTemplate := `// export function use{handlerName}({props}) {
//     return useServerMutation{<}{TData}{TVar}{>}({
//         endpoint: API_ENDPOINTS.{groupName}.{handlerName}.endpoint{endpointSuffix},
//         method: API_ENDPOINTS.{groupName}.{handlerName}.methods[%d],
//         mutationKey: [API_ENDPOINTS.{groupName}.{handlerName}.key],
//         onSuccess: async () => {
// 
//         },
//     })
// }

`

	tmpGroupTmpls := make(map[string][]string)

	for _, filename := range filenames {
		routes := groupedHandlers[filename]
		if len(routes) == 0 {
			continue
		}

		if lo.EveryBy(routes, func(route *RouteHandler) bool {
			return route.Api == nil || len(route.Api.Methods) == 0
		}) {
			continue
		}

		f.WriteString("//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n")
		f.WriteString(fmt.Sprintf("// %s\n", strings.TrimSuffix(filename, ".go")))
		f.WriteString("//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n")

		tmpls := make([]string, 0)
		for _, route := range groupedHandlers[filename] {
			if route.Api == nil || len(route.Api.Methods) == 0 {
				continue
			}

			for i, method := range route.Api.Methods {

				tmpl := ""

				if method == "GET" {
					getTemplate := strings.ReplaceAll(queryTemplate, "{handlerName}", strings.TrimPrefix(route.Name, "Handle"))
					getTemplate = strings.ReplaceAll(getTemplate, "{groupName}", strings.ToUpper(strings.TrimSuffix(filename, ".go")))
					getTemplate = strings.ReplaceAll(getTemplate, "{method}", "GET")
					tmpl = getTemplate
				}

				if method == "POST" || method == "PATCH" || method == "PUT" || method == "DELETE" {
					mutTemplate := strings.ReplaceAll(mutationTemplate, "{handlerName}", strings.TrimPrefix(route.Name, "Handle"))
					mutTemplate = strings.ReplaceAll(mutTemplate, "{groupName}", strings.ToUpper(strings.TrimSuffix(filename, ".go")))
					mutTemplate = strings.ReplaceAll(mutTemplate, "{method}", method)
					tmpl = mutTemplate
				}

				tmpl = strings.ReplaceAll(tmpl, "%d", strconv.Itoa(i))

				if len(route.Api.ReturnTypescriptType) == 0 {
					tmpl = strings.ReplaceAll(tmpl, "{<}", "")
					tmpl = strings.ReplaceAll(tmpl, "{TData}", "")
					tmpl = strings.ReplaceAll(tmpl, "{TVar}", "")
					tmpl = strings.ReplaceAll(tmpl, "{>}", "")
				} else {
					tmpl = strings.ReplaceAll(tmpl, "{<}", "<")
					tmpl = strings.ReplaceAll(tmpl, "{TData}", route.Api.ReturnTypescriptType)
					tmpl = strings.ReplaceAll(tmpl, "{>}", ">")
				}

				if len(route.Api.Params) == 0 {
					tmpl = strings.ReplaceAll(tmpl, "{endpointSuffix}", "")
					tmpl = strings.ReplaceAll(tmpl, "{props}", "")
				} else {
					props := ""
					for _, param := range route.Api.Params {
						props += fmt.Sprintf(`%s: %s, `, param.JsonName, param.TypescriptType)
					}
					tmpl = strings.ReplaceAll(tmpl, "{props}", props[:len(props)-2])
					endpointSuffix := ""
					for _, param := range route.Api.Params {
						endpointSuffix += fmt.Sprintf(`.replace("{%s}", String(%s))`, param.JsonName, param.JsonName)
					}
					tmpl = strings.ReplaceAll(tmpl, "{endpointSuffix}", endpointSuffix)
				}

				if len(route.Api.BodyFields) == 0 {
					tmpl = strings.ReplaceAll(tmpl, "{TVar}", "")
				} else {
					tmpl = strings.ReplaceAll(tmpl, "{TVar}", fmt.Sprintf(", %s", strings.TrimPrefix(route.Name, "Handle")+"_Variables"))
				}

				tmpls = append(tmpls, tmpl)
				f.WriteString(tmpl)

			}

		}
		tmpGroupTmpls[strings.TrimSuffix(filename, ".go")] = tmpls
	}

	//for filename, tmpls := range tmpGroupTmpls {
	//	hooksF, err := os.Create(filepath.Join("../seanime-web/src/api/hooks", filename+".hooks.ts"))
	//	if err != nil {
	//		panic(err)
	//	}
	//	defer hooksF.Close()
	//
	//	for _, tmpl := range tmpls {
	//		hooksF.WriteString(tmpl)
	//	}
	//}

}

func generateEventFile(eventDir string, endpointsMap map[string]string) {
	fp := filepath.Join(eventDir, goEndpointsFileName)
	file, err := os.Create(fp)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	file.WriteString("// This code was generated by codegen/main.go. DO NOT EDIT.\n")
	file.WriteString("package events\n\n")

	// file.WriteString(fmt"var Endpoint = map[string]string{\n")

	endpoints := []string{}
	for endpoint := range endpointsMap {
		endpoints = append(endpoints, endpoint)
	}
	slices.SortStableFunc(endpoints, func(i, j string) int {
		return strings.Compare(i, j)
	})

	goFmtSpacing := ""

	file.WriteString("const (\n")
	for _, endpoint := range endpoints {
		file.WriteString(fmt.Sprintf("    %sEndpoint%s= \"%s\"\n", endpoint, goFmtSpacing, endpointsMap[endpoint]))
	}
	file.WriteString(")\n")

	cmd := exec.Command("gofmt", "-w", fp)
	cmd.Run()

}

func writeParamField(f *os.File, handler *RouteHandler, param *RouteHandlerParam) {
	if len(param.Descriptions) > 0 {
		writeLine(f, "\t/**")
		for _, cmt := range param.Descriptions {
			writeLine(f, fmt.Sprintf("\t *  %s", strings.TrimSpace(cmt)))
		}
		writeLine(f, "\t */")
	}
	fieldSuffix := ""
	if !param.Required {
		fieldSuffix = "?"
	}
	writeLine(f, fmt.Sprintf("\t%s%s: %s", param.JsonName, fieldSuffix, param.TypescriptType))
}

func getEndpointKey(s string, groupName string) string {
	s = strings.TrimPrefix(s, "Handle")
	var result string
	for i, v := range s {
		if i > 0 && v >= 'A' && v <= 'Z' {
			result += "-"
		}

		result += string(v)
	}
	result = strings.ToLower(result)
	if strings.Contains(result, "t-v-d-b") {
		result = strings.Replace(result, "t-v-d-b", "tvdb", 1)
	}
	if strings.Contains(result, "m-a-l") {
		result = strings.Replace(result, "m-a-l", "mal", 1)
	}
	return strings.ReplaceAll(groupName, "_", "-") + "-" + result
}

func writeLine(file *os.File, template string) {
	template = strings.ReplaceAll(template, "\t", space)
	file.WriteString(fmt.Sprintf(template + "\n"))
}
