package gen

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
	GO "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/translate/got"
	TS "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/translate/tst"
)

func CreateProject(mode string) (*Project, error) {

	var (
		err              error
		path             string
		id               string
		files            []os.FileInfo
		entity           *Entity
		resource         *Resource
		directory        string
		paramAcceptFiled = map[string]bool{}
		p                = &Project{
			Mode:             mode,
			SchemasRef:       map[string]*Entity{},
			Icons:            map[string]string{},
			ReplaceWhenEmpty: map[string]bool{},
			OmitEmpty:        map[string]bool{},
			FormatMap:        map[string]string{},
			Queries:          &QueryDef{},
			Translators: map[string]TranslationFn{
				"go":       GO.Translate,
				"angular6": TS.Translate,
			},
			TranslatorBuildOptionsMap: map[string]func(*Project, *BuildOptions) (*BuildSet, error){
				"go":       GO.Build,
				"angular6": TS.Build,
			},
		}
	)

	if directory, err = os.Getwd(); err != nil {
		return nil, err
	}

	p.CurrentDirectory = directory

	// Carrega a configuracao default do projeto
	// root := []string{fmt.Sprintf("%s/project.json", directory)}

	// if mode != "" {
	// 	root = append(root, fmt.Sprintf("%s/project.%s.json", directory, mode))
	// }

	// for _, file := range root {
	// 	if file == "" {
	// 		continue
	// 	}
	// 	if err = ParseJson(file, p); err != nil {
	// 		return nil, err
	// 	}
	// }

	if err = JsonParseMode(fmt.Sprintf("%s/project", directory), mode, p); err != nil {
		return nil, err
	}

	if err = ParseJson("queries.json", &p.Queries); err != nil {
		p.Queries.Blacklistwords = map[string][]string{}
		p.Queries.Queries = map[string]string{}
	}

	if err = loadQueries(p, directory); err != nil {
		return nil, err
	}

	if err = expandQueries(p); err != nil {
		return nil, err
	}

	if err = ParseJson("common.json", &p.Resource); err != nil {
		p.Resource = &Resource{}
	}

	for id, v := range p.Resource.CommonParams {
		if v.ID == "" {
			v.ID = id
		}
	}

	if err = JsonParseMode("env/environment", mode, &p.Environment); err != nil {
		return nil, err
	}

	for id, variable := range p.Environment {
		variable.ID = id
	}

	// Carrega os arquivos de entidades localizados na pasta entities
	path = fmt.Sprintf("%s/entities", directory)
	if files, err = GetFiles(path); err == nil {
		fmt.Println("Read entities...")
		for _, file := range files {
			if file.Name()[0] == '*' {
				continue
			}
			if !file.IsDir() {
				entity = &Entity{}
				if err = ParseJson(filepath.Join(path, file.Name()), entity); err != nil {
					return nil, err
				}
				p.Schemas = append(p.Schemas, entity)
			}
		}
	}

	// Carrega os arquivos de recursos localizados na pasta resources
	path = fmt.Sprintf("%s/resources", directory)
	if files, err = GetFiles(path); err == nil {
		fmt.Println("Read resources...")
		for _, file := range files {
			if file.Name()[0] == '*' {
				continue
			}

			resource = &Resource{}
			if err = ParseJson(filepath.Join(path, file.Name()), resource); err != nil {
				return nil, err
			}
			if file.Name() == "global.json" {
				p.Resource = resource

			} else {
				p.Resources = append(p.Resources, resource)
			}
		}
	}
	// Inicializa variaveis do projeto
	if p.OmitEmpty == nil {
		p.OmitEmpty = map[string]bool{}
	}

	p.ReplaceWhenEmpty = map[string]bool{
		"json": true,
		"bson": true,
	}

	// p.OmitEmpty["json"] = true
	// p.OmitEmpty["bson"] = true

	// Atualiza o mapeamento de entidades e resources
	p.SchemasRef = map[string]*Entity{}

	for _, s := range p.Schemas {
		id = GenericPart.ReplaceAllString(s.ID, "")
		for _, p := range s.Properties {
			if err = p.ParseAutogenerate(); err != nil {
				return nil, err
			}
		}
		p.SchemasRef[id] = s
		p.SchemasRef[id+"Reference"] = SchemaReference(s)
		p.SchemasRef[id+"Input"] = SchemaInput(s)
	}

	for _, r := range p.Resources {

		format := map[string]*Value{}

		for _, f := range r.Formats {
			switch f.Fields {
			case "@all":
				f.Fields = ""
			case "@reference":
				if ref, found := p.SchemasRef[r.Entity+"Reference"]; found {
					fields := []string{}

					for _, propertie := range ref.Properties {
						fields = append(fields, propertie.ID)
					}

					f.Fields = strings.Join(fields, ",")
				}
			}

			format[f.Value] = f
		}

		for _, m := range r.Methods {
			m.Resource = r

			m.Entity = r.Entity

			m.Parameters = map[string]*Parameter{}

			if p.Resource.CommonParams == nil {
				continue
			}

			for _, p1 := range m.ParametersString {

				if p2, ok := p.Resource.CommonParams[p1]; ok {
					m.Parameters[p1] = p2
				} else {
					panic(fmt.Errorf("Param '%s' not defined!", p1))
				}
			}

			for _, param := range m.Parameters {
				if _, ok := paramAcceptFiled[param.ID]; ok {
					continue
				}
				paramAcceptFiled[param.ID] = true

				if param.ConvertTo == "" {
					param.ConvertTo = param.Type
				}

				// if param.Validation != nil {
				// 	if param.Validation.AcceptRef != nil {
				// 		for _, accept := range param.Validation.AcceptRef {
				// 			param.Validation.Accept = append(param.Validation.Accept, format[accept])
				// 		}
				// 	}
				// 	if param.Validation.RejectRef != nil {
				// 		for _, reject := range param.Validation.RejectRef {
				// 			param.Validation.Reject = append(param.Validation.Reject, format[reject])
				// 		}
				// 	}
				// }
			}
		}
	}

	if err = interpolateProject(p); err != nil {
		return nil, err
	}

	// o, _ := json.Marshal(p)

	readBuildVersion(p, directory)

	return p, nil
}

func readBuildVersion(project *Project, directory string) {
	var (
		filename = fmt.Sprintf("%s/buildversion", directory)
		build    = ""
		buildInt = 0
		err      error
	)
	// defer func() {
	// 	FilePutContents(filename, strconv.Itoa(buildInt), 0777)
	// }()

	if build, err = FileGetContents(filename); err != nil {
		build = "0"
	}

	buildInt, _ = strconv.Atoi(build)

	buildInt++

	project.BuildVersion = fmt.Sprintf("%s.%d", project.Version, buildInt)
}

func SchemaReference(s *Entity) *Entity {
	x := *s
	x.Properties = []*Propertie{}
	for _, p := range s.Properties {

		if p.Reference {
			x.Properties = append(x.Properties, p)
		}
	}
	return &x
}

func SchemaInput(s *Entity) *Entity {
	x := *s
	x.Properties = []*Propertie{}
	for _, p := range s.Properties {
		if !p.Readonly {
			x.Properties = append(x.Properties, p)
		}
	}
	return &x
}

func interpolateProject(project *Project) (err error) {
	data := map[string]interface{}{
		"baseUrl": project.BaseURL,
	}

	for _, resource := range project.Resources {
		data["entity"] = resource.Entity
		for _, method := range resource.Methods {

			method.Request = ResolveParams(method.Request, data)

			method.Response = ResolveParams(method.Response, data)

			// replace baseurl in scopes
			scopes := []string{}
			for _, scope := range method.Scopes {
				scopes = append(
					scopes,
					ResolveParams(scope, data),
				)
			}
			method.Scopes = scopes
		}
	}

	// enviroment variables

	//

	for _, variable := range project.Environment {
		variable.Default = ResolveParams(variable.Default, data)
	}

	return
}

var (
	expandTranscludeExpression = regexp.MustCompile(`"\$\._transclude_":(true|"\w+")`)
	expandVarExpression        = regexp.MustCompile(`"\$(\.\w+)"`)
	removeAllSpacesExpression  = regexp.MustCompile(`(\s|\t|\r|\n)+`)
)

func loadQueries(project *Project, directory string) (err error) {
	var (
		files    []os.FileInfo
		data     string
		queryKey string
	)
	path := fmt.Sprintf("%s/queries", directory)

	if files, err = GetFiles(path); err == nil {
		fmt.Println("Read queries...")
		for _, file := range files {
			if file.Name()[0] == '*' {
				continue
			}
			// resource = &Resource{}
			if data, err = FileGetContents(filepath.Join(path, file.Name())); err != nil {
				return
			}

			queryKey = strings.ReplaceAll(strings.ReplaceAll(file.Name(), "_", ":"), ".json", "")

			data = removeAllSpacesExpression.ReplaceAllString(data, "")

			data = expandTranscludeExpression.ReplaceAllStringFunc(data, func(input string) string {
				parts := strings.Split(input, ":")
				selector := strings.Trim(parts[len(parts)-1], `"`)
				return fmt.Sprintf("{._transclude_%s}", selector)
			})

			data = expandVarExpression.ReplaceAllStringFunc(data, func(input string) string {
				input = strings.TrimLeft(strings.TrimRight(input, `"`), `"$`)
				return fmt.Sprintf("{{%s}}", input)
			})

			project.Queries.Queries[queryKey] = data
		}
	}
	return
}
func expandQueries(project *Project) (err error) {
	var (
		newquery string
		found    bool
	)

	for key, query := range project.Queries.Queries {
		if len(query) > 0 && query[0] == '$' {

			if newquery, found = project.Queries.Queries[query[1:]]; !found {
				panic(fmt.Errorf("Query `%s` not defined", query[1:]))
			}

			project.Queries.Queries[key] = newquery
		}
	}
	return
}
