package golinters

import (
	"context"
	"fmt"
	"go/ast"
	"go/token"

	"github.com/golangci/golangci-lint/pkg/lint/linter"
	"github.com/golangci/golangci-lint/pkg/result"
)

type Nakedret struct{}

func (Nakedret) Name() string {
	return "nakedret"
}

func (Nakedret) Desc() string {
	return "Finds naked returns in functions greater than a specified function length"
}

type nakedretVisitor struct {
	maxLength int
	f         *token.FileSet
	issues    []result.Issue
}

func (v *nakedretVisitor) processFuncDecl(funcDecl *ast.FuncDecl) {
	file := v.f.File(funcDecl.Pos())
	functionLineLength := file.Position(funcDecl.End()).Line - file.Position(funcDecl.Pos()).Line

	// Scan the body for usage of the named returns
	for _, stmt := range funcDecl.Body.List {
		s, ok := stmt.(*ast.ReturnStmt)
		if !ok {
			continue
		}

		if len(s.Results) != 0 {
			continue
		}

		file := v.f.File(s.Pos())
		if file == nil || functionLineLength <= v.maxLength {
			continue
		}
		if funcDecl.Name == nil {
			continue
		}

		v.issues = append(v.issues, result.Issue{
			FromLinter: Nakedret{}.Name(),
			Text: fmt.Sprintf("naked return in func `%s` with %d lines of code",
				funcDecl.Name.Name, functionLineLength),
			Pos: v.f.Position(s.Pos()),
		})
	}
}

func (v *nakedretVisitor) Visit(node ast.Node) ast.Visitor {
	funcDecl, ok := node.(*ast.FuncDecl)
	if !ok {
		return v
	}

	var namedReturns []*ast.Ident

	// We've found a function
	if funcDecl.Type != nil && funcDecl.Type.Results != nil {
		for _, field := range funcDecl.Type.Results.List {
			for _, ident := range field.Names {
				if ident != nil {
					namedReturns = append(namedReturns, ident)
				}
			}
		}
	}

	if len(namedReturns) == 0 || funcDecl.Body == nil {
		return v
	}

	v.processFuncDecl(funcDecl)
	return v
}

func (lint Nakedret) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
	var res []result.Issue
	for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
		v := nakedretVisitor{
			maxLength: lintCtx.Settings().Nakedret.MaxFuncLines,
			f:         f.Fset,
		}
		ast.Walk(&v, f.F)
		res = append(res, v.issues...)
	}

	return res, nil
}