package got

import (
	"text/template"

	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
	G "github.com/dave/jennifer/jen"
)

var (
	updateStmtsTmpl *template.Template
	updateStmtsErr  error
)

func init() {

	updateStmtsTmpl, updateStmtsErr = ParseTemplate(`
	var (
		entity = &models.{{.entity}}Input{}
	)

	if err = api.ReadJson(ctx, entity); err != nil {
		return
	}
	
	entity.SetMode("update")

	if err = api.Validate(entity); err != nil {
		return
	}

	values := ctx.Values()
	entity.LastUpdate = api.NowUnix()
	user := values.Get("$user.ref").(*models.UserReference)
	entity.UpdatedBy = user
	values.Set("entity", entity)
	
	{{if .preconditions}}
		if _, err = executeAction(
			ctx,
			{{.preconditions}},
		); err != nil {
			return
		}
	{{end}}

	filter := values.Get("$filter").(*api.Filter)
	filter.DB = "{{.dbName}}"
	filter.Collection = "{{.collectionName}}"
	filter.Entity = entity

	{{if .beforePersist}} if err = BeforePersist{{.entity}}Update(ctx, resp); err != nil {
		return
	}{{end}}

	if _, err = models.Api.UpdateOne(filter); err != nil {
		return
	}

	{{if .hasUpdateRelation}} // Cria uma thread que executa a atualizaca das referências.
	go func() {
		UpdateRelation{{.entity}}(filter)
	}()	{{end}}

	{{if .beforeResponse}}
	if resp, err = executeAction(
		ctx,
		{{.beforeResponse}},
	); err != nil || resp != nil {
		return
	}
	{{end}}
	
	resp = entity
	return`)

	if updateStmtsErr != nil {
		panic(updateStmtsErr)
	}
}

var (
	GenUpdateStmts = &Middleware{
		Id:   "implement",
		Type: "method",
		Fn: func(ctx *MiddlewareContext) error {
			var (
				project          = ctx.Project
				method           = ctx.Method
				dbName           = project.GetEntityDB(method.Entity)
				collectionName   = project.GetCollection(method.Entity)
				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
				beforeSend       = method.Hook("beforeSend")
				beforePersist    = method.Hook("beforePersist")
				relations        = []map[string]interface{}{}
				context          = map[string]interface{}{
					"dbName":            dbName,
					"collectionName":    collectionName,
					"entity":            method.Entity,
					"dependenceMethod":  dependenceMethod,
					"beforeSend":        beforeSend,
					"beforePersist":     beforePersist,
					"relations":         &relations,
					"hasUpdateRelation": SR.Has(method.Entity),
					"preconditions":     parseMethodActions(method.Preconditions),
					"beforeResponse":    parseMethodActions(method.BeforeResponse),
				}
			)

			generateHookCall(project, method, "beforePersist")

			generateHookCall(project, method, "beforeSend")

			// Nome do metodo que verifica se a entidade tem dependencias
			out, _ := TemplateToString(updateStmtsTmpl, context)
			afterMethod := ctx.Statement.Block(G.Id(out)).Line()

			if body, err := createUpdateRelationMethod(method, context); err == nil {
				afterMethod.Id(body)
			}

			return nil
		},
	}
)

// updateRelationStmtsTmpl, updateStmtsErr = ParseTemplate(`
// func UpdateRelation{{.entity}}(options *api.Filter) {
// 	var (
// 		filter *api.Filter
// 		err    *errs.Error
// 		entity = &{{.entity}}Reference{}
// 		wg     = sync.WaitGroup{}
// 	)

// 	options.Entity = entity

// 	if _, err = models.Api.FindOne(options); err != nil {
// 		return
// 	}

// 	entity.Id = entity.Id.(primitive.ObjectID).Hex()
// 	{{range .relations}}
// 	wg.Add(1)
// 	go func(){
// 		defer wg.Done()

// 		filter = &api.Filter{
// 			Collection: "{{.Collection}}",
// 			DB:         "{{.DB}}",
// 			Patchs:     &bson.A{bson.M{"$set": bson.M{"{{.path}}": entity}}},
// 			Query:      &bson.M{"{{.attr}}._id": entity.Id},
// 		}

// 		if _, err = models.Api.PatchMany(filter); err != nil {
// 			fmt.Println(err.Error())
// 		}
// 	}()
// 	{{end}}

// 	wg.Wait()
// }`)

// if updateStmtsErr != nil {
// 	panic(updateStmtsErr)
// }
// if hasUpdateRelation && !updateRelationMethodDefined {
// 	relationFunctionDefined[updRelation] = true
// 	for _, relation := range SR.Get(method.Entity) {

// 		if relation.DB == "" || relation.Collection == "" {
// 			api.LogWarning(0, fmt.Sprintf("Relation DB or Collection can't be empty! %s -> %s", relation.Target, relation.Source))
// 			continue
// 		}

// 		relations = append(relations, map[string]interface{}{
// 			"DB":         relation.DB,
// 			"Collection": relation.Collection,
// 			"attr":       relation.Attr,
// 			"path":       fmt.Sprintf("%s%s", relation.Attr, indexMap[relation.IsArray]),
// 		})

// 	}

// 	out, _ = TemplateToString(updateRelationStmtsTmpl, context)
// 	afterMethod.Id(out)
// }

// j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
// 	// Especifica a collection
// 	G.Id("DB"):         G.Lit(relation.DB),
// 	G.Id("Collection"): G.Lit(relation.Collection),

// 	// Especifica a query
// 	G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
// 		G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
// 	}),

// 	// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
// 	// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
// 	// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
// 	// 	}),
// 	// }),
// 	G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
// 		G.Qual(BSON, "M").Values(G.Dict{
// 			G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
// 				G.Lit(attrId + index): G.Id("f").Dot("Entity"),
// 			}),
// 		}),
// 	),
// }).Line().If(
// G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
// G.Id("filter"),
// ),
// G.Id("err").Op("!=").Id("nil"),
// ).Block(
// G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
// ).Line().Line()

// k.Add(j)
// .Do(func(s *G.Statement) {

// 	_, found := relationFunctionDefined[updRelation]
// 	if !createUpdateRelation || found {
// 		return
// 	}

// 	s.Comment(updRelation + " atualiza as referências da entidade " + method.Entity).Line()

// 	s.Func().Id(updRelation).Params(
// 		G.Id("f").Op("*").Qual(API_URL, "Filter"),
// 	).Block(
// 		G.Var().Defs(
// 			G.Id("filter").Op("*").Qual(API_URL, "Filter"),
// 			G.Id("err").Op("*").Qual(API_ERROR, "Error"),
// 		),
// 		// atualiza o filtro para a entidade de referencia
// 		G.Id("f").Dot("Entity").Op("=").Op("&").Id(method.Entity+"Reference").Values(),

// 		// Carrega os dados do banco para atualizar nas entidades relacionadas
// 		G.If(
// 			G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("FindOne").Call(
// 				G.Id("f"),
// 			),
// 			G.Id("err").Op("!=").Id("nil"),
// 		).Block(
// 			G.Return(),
// 		),
// 		G.Do(func(k *G.Statement) { // Adiciona as regras de atualizacao
// 			var (
// 				j     *G.Statement
// 				index string
// 			)

// 			for _, relation := range SR.Get(method.Entity) {
// 				if relation.DB == "" || relation.Collection == "" {
// 					api.LogWarning(0, fmt.Sprintf("Relation DB or Collection can't be empty! %s -> %s", relation.Target, relation.Source))
// 					continue
// 				}

// 				// spew.Dump(relation)
// 				// panic("sss")
// 				// e := project.GetSchema(relation)
// 				attrId := relation.Attr
// 				if relation.IsArray {
// 					index = ".$"
// 				} else {
// 					index = ""
// 				}

// 				j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
// 					// Especifica a collection
// 					G.Id("DB"):         G.Lit(relation.DB),
// 					G.Id("Collection"): G.Lit(relation.Collection),

// 					// Especifica a query
// 					G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
// 						G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
// 					}),

// 					// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
// 					// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
// 					// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
// 					// 	}),
// 					// }),
// 					G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
// 						G.Qual(BSON, "M").Values(G.Dict{
// 							G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
// 								G.Lit(attrId + index): G.Id("f").Dot("Entity"),
// 							}),
// 						}),
// 					),
// 				}).Line().If(
// 					G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
// 						G.Id("filter"),
// 					),
// 					G.Id("err").Op("!=").Id("nil"),
// 				).Block(
// 					G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
// 				).Line().Line()

// 				k.Add(j)
// 			}
// 		}),
// 	)
// })

// var (
// 	// UpdateRelationRotine *G.Statement
// 	method               = ctx.Method
// 	project              = ctx.Project
// 	updRelation          = UPDATE_RELATION + method.Entity
// 	createUpdateRelation = false
// 	entity               = method.Request
// 	responseEntity       = GenericPart.ReplaceAllString(method.Response, "")
// )
// if entity == "" {
// 	panic("Modelo de request não definido em UpdateStmt!")
// }
// if responseEntity == "" {
// 	panic("Modelo de response não definido!")
// }

// ctx.Statement.Block(
// 	// Declaracao das variaveis
// 	G.Var().Defs(
// 		// G.Id("err").Op("*").Qual(API_ERROR, "Error"),
// 		G.Id("entity").Op("=").Op("&").Id(entity).Values(),
// 	).Line(),
// 	// Fazendo o parse do body
// 	G.If(
// 		G.Id("err").Op("=").Qual(API_URL, "ReadJson").Call(G.Id("ctx"), G.Id("entity")),
// 		G.Id("err").Op("!=").Id("nil"),
// 	).Block(
// 		// G.Id("api").Dot("Falha").Call(
// 		// 	G.Id("ctx"),
// 		// 	G.Id("api").Dot("ErrGeneral"),
// 		// 	G.Id("err").Dot("Error").Call(),
// 		// 	G.Nil(),
// 		// ),
// 		// G.Return(G.Id("err")),
// 		G.Return(),
// 	),
// 	G.Do(func(s *G.Statement) {
// 		entity := ctx.Project.EntityDesc(method.Entity)
// 		if entity != nil && entity.HasMode {
// 			s.Add(G.Id(`entity.SetMode("create")`))
// 		}
// 	}),
// 	G.If(
// 		G.Id("err").Op("=").Qual(API_URL, "Validate").Call(G.Id("entity")),
// 		G.Id("err").Op("!=").Id("nil"),
// 	).Block(
// 		// G.Return(G.Id("err")),
// 		G.Return(),
// 	).Line(),
// 	// Captura a base de values
// 	G.Id("values").Op(":=").Id("ctx").Dot("Values").Call(),

// 	// Gera as atribuicoes de variaveis que seram atualizadas (usuario e ultimo update quando existir)
// 	G.Do(func(part *G.Statement) {
// 		entity := ctx.Project.GetSchema(method.Entity)
// 		user := false
// 		for _, prop := range entity.Properties {
// 			if def, ok := prop.Autogenerate["update"]; ok {
// 				switch def.Type {
// 				case "user":
// 					if !user {
// 						part.Add(G.Id(`user := values.Get("$user.ref").(*UserReference)`).Line())
// 						user = true
// 					}

// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id("user").Line())
// 				case "now":
// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
// 				}
// 			}
// 		}
// 	}).Line(),

// 	// Carrega o filtro do contexto para adicionar a entidade
// 	G.Id("filter").Op(":=").Id("values").Dot("Get").Call(
// 		G.Lit("$filter"),
// 	).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),

// 	// Adiciona a entidade
// 	G.Id("filter").Dot("DB").Op("=").Lit(ctx.Project.GetEntityDB(method.Entity)),
// 	G.Id("filter").Dot("Collection").Op("=").Lit(ctx.Project.GetCollection(method.Entity)),
// 	G.Id("filter").Dot("Entity").Op("=").Id("entity").Line(),
// 	generateHookCall(project, method, "beforePersist"),
// 	// Inseri a entidade na coleção e verifica se a operação ocorreu com exito
// 	G.If(
// 		G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("UpdateOne").Call(
// 			// G.Lit(ctx.Project.GetCollection(method.Entity)),
// 			G.Id("filter"),
// 		),
// 		G.Id("err").Op("!=").Id("nil"),
// 	).Block(
// 		// G.Id("api").Dot("Falha").Call(
// 		// 	G.Id("ctx"),
// 		// 	G.Id("api").Dot("ErrGeneral"),
// 		// 	G.Id("err").Dot("Error").Call(),
// 		// 	G.Nil(),
// 		// ),
// 		// bson.IsObjectIdHex(m.Id.Hex())
// 		// G.Return(G.Id("err")),
// 		G.Return(),
// 	),
// 	// Cria a rotina de atualização de relacionamentos
// 	G.Do(func(s *G.Statement) {

// 		if SR.Has(method.Entity) {
// 			createUpdateRelation = true

// 			relationFunctionDefined[updRelation] = true
// 			s.Comment("Cria uma thread que executa a atualizaca das referências.").Line().Go().Func().Params().Block(
// 				G.Id(updRelation).Call(G.Id("filter")),
// 			).Call()

// 		}
// 	}),

// 	// Envia a resposta pro usuario em caso de sucesso
// 	// G.Line().Id("resp").Op("=").Id(responseEntity).Values(G.Dict{
// 	// 	G.Id("Entity"): G.Id("entity"),
// 	// }),
// 	G.Line().Id("resp").Op("=").Id("entity"),
// 	generateHookCall(project, method, "beforeSend"),
// 	// G.Return(G.Nil()),
// 	G.Return(),
// ).Line()
