package logger

import (
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"strings"
)

func (info *LoggerInfo) checkCompleteness() bool {
	return info.CallerType != "" && info.Layer != "" && info.ModuleType != ""
}

func (info *LoggerInfo) setLayer(layer LayerType) {
	if info.Layer == "" {
		info.Layer = layer
	}
}

func (info *LoggerInfo) setCaller(caller CallerType) {
	if info.CallerType == "" {
		info.CallerType = caller
	}
}

func (info *LoggerInfo) setModuleType(module ModuleType) {
	if info.ModuleType == "" {
		info.ModuleType = module
	}
}

func (info *LoggerInfo) setName(name string) {
	if info.Name == "" {
		info.Name = name
	}
}

func tryToIndentCliType(info *LoggerInfo, stmts []ast.Stmt) {
	for _, stmt := range stmts {
		if assign, ok := stmt.(*ast.AssignStmt); ok {
			if callEx, ok := assign.Rhs[0].(*ast.CallExpr); ok {
				if funcExp, ok := callEx.Fun.(*ast.SelectorExpr); ok {
					if funcExp.Sel.Name == "NewTransportClient" {
						if refExpr, ok := callEx.Args[0].(*ast.UnaryExpr); ok && refExpr.Op == token.AND {
							if structExpr, ok := refExpr.X.(*ast.CompositeLit); ok {
								for _, elt := range structExpr.Elts {
									if keyValueEx, ok := elt.(*ast.KeyValueExpr); ok {
										if key, ok := keyValueEx.Key.(*ast.Ident); ok {
											// TODO: switch for all ClientInfo fields
											switch key.Name {
											case "Type":
												if value, ok := keyValueEx.Value.(*ast.BasicLit); ok {
													switch value.Value {
													case "\"SERVER\"":
														info.setModuleType(ServerType)
														return
													case "\"CLIENT\"":
														info.setModuleType(ClientType)
														return
													case "\"STANDALONE\"":
														info.setModuleType(StandaloneType)
														return
													default:
														return
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	return
}

func analyzeFuncName(info *LoggerInfo, name string) {
	switch name {
	case "EventHandler", "RequestHandler", "ResponseHandler", "GetClients", "WhoIsMaster", "SendRedirectRequest":
		info.setLayer(TransportLayer)
		info.setModuleType(OtherType)
		info.setName("transport")
	case "AmTemplatesHandler", "NewAdminServer", "WafBlacklistHandler", "WafWhitelistHandler", "WafHandler":
		info.setLayer(OtherLayer)
		info.setModuleType(OtherType)
		info.setName("admin-panel")
	case "NewFileserver", "UploadHandler":
		info.setLayer(OtherLayer)
		info.setModuleType(OtherType)
		info.setName("fs")
	}
}

func (logger *loggerImpl) parseGoFile(info *LoggerInfo, name string) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, name, nil, parser.AllErrors)
	if err != nil {
		logger.Warnf("Can't parse go file. File: %s. Error: %v", name, err)
		return
	}

	// extracting maximum info from package name
	if info.CallerType == "" {
		packageName := f.Name.Name
		if packageName == "main" {
			info.setCaller(AppCaller)
		} else {
			switch packageName {
			case "client": // transport library
				info.setCaller(LibCaller)
				info.setLayer(TransportLayer)
			case "requestor", "download_lib", "GOparser": // other libraries
				info.setCaller(LibCaller)
				info.setLayer(OtherLayer)
				info.setModuleType(OtherType)
			default: // other packages
				info.setCaller(AppCaller)
			}
		}
	}

	for _, decl := range f.Decls {
		if f, ok := decl.(*ast.FuncDecl); ok {
			if info.Layer == "" || info.ModuleType == "" {
				analyzeFuncName(info, f.Name.Name)
			}
			if info.ModuleType == "" {
				tryToIndentCliType(info, f.Body.List)
			}
		}
	}
	return
}

func (logger *loggerImpl) extractFromConfig(info *LoggerInfo) {
	config := logger.initConfig()
	moduleType := config.Get("app", "type").String("")
	name := config.Get("app", "name").String("")
	logger.Debugf("module type: %s. name: %s", moduleType, name)
	switch moduleType {
	case "CLIENT":
		info.setModuleType(ClientType)
	case "SERVER":
		info.setModuleType(ServerType)
	case "STANDALONE":
		info.setModuleType(StandaloneType)
	default:
		logger.Debugf("Can't find transport client type from config")
		break
	}
	info.setName(name)
	if moduleType != "" || name != "" {
		info.setLayer(ApplicationLayer)
	}
}

func (logger *loggerImpl) startIdentify(info *LoggerInfo) {
	logger.Debugf("starting identify caller")

	srcDirPath := ""
	lastDirInx := strings.LastIndex(info.callerFile, "/")
	if lastDirInx == -1 {
		srcDirPath = "./"
	} else {
		lastDirInx++
		srcDirPath = info.callerFile[:lastDirInx]
	}
	srcDir, err := os.Open(srcDirPath)
	if err != nil {
		logger.Warnf("Can't open source directory %s. Error: %v", srcDirPath, err)
		return
	}

	files, err := srcDir.Readdirnames(-1)
	if err != nil {
		logger.Warnf("Can't reading source directory %s. Error: %v", srcDirPath, err)
		return
	}

	for _, file := range files {
		srcFile := srcDirPath + file
		if strings.HasSuffix(srcFile, ".go") {
			logger.parseGoFile(info, srcFile)
			if info.checkCompleteness() {
				break
			}
		}
	}

	if info.CallerType != LibCaller {
		//Getting info from config
		logger.extractFromConfig(info)
	}

	logger.Infof("End indetify lib caller. Info: %+v", info)
}
