package processors

import (
	"path/filepath"
	"regexp"
	"strings"

	"github.com/pkg/errors"

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

type SkipDirs struct {
	patterns    []*regexp.Regexp
	log         logutils.Log
	skippedDirs map[string][]string // regexp to dir mapping
	absArgsDirs []string
}

var _ Processor = SkipFiles{}

const goFileSuffix = ".go"

func NewSkipDirs(patterns []string, log logutils.Log, runArgs []string) (*SkipDirs, error) {
	var patternsRe []*regexp.Regexp
	for _, p := range patterns {
		patternRe, err := regexp.Compile(p)
		if err != nil {
			return nil, errors.Wrapf(err, "can't compile regexp %q", p)
		}
		patternsRe = append(patternsRe, patternRe)
	}

	if len(runArgs) == 0 {
		runArgs = append(runArgs, "./...")
	}
	var absArgsDirs []string
	for _, arg := range runArgs {
		base := filepath.Base(arg)
		if base == "..." || strings.HasSuffix(base, goFileSuffix) {
			arg = filepath.Dir(arg)
		}

		absArg, err := filepath.Abs(arg)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to abs-ify arg %q", arg)
		}
		absArgsDirs = append(absArgsDirs, absArg)
	}

	return &SkipDirs{
		patterns:    patternsRe,
		log:         log,
		skippedDirs: map[string][]string{},
		absArgsDirs: absArgsDirs,
	}, nil
}

func (p SkipDirs) Name() string {
	return "skip_dirs"
}

func (p *SkipDirs) Process(issues []result.Issue) ([]result.Issue, error) {
	if len(p.patterns) == 0 {
		return issues, nil
	}

	return filterIssues(issues, p.shouldPassIssue), nil
}

func (p *SkipDirs) shouldPassIssue(i *result.Issue) bool {
	if filepath.IsAbs(i.FilePath()) {
		p.log.Warnf("Got abs path in skip dirs processor, it should be relative")
		return true
	}

	issueRelDir := filepath.Dir(i.FilePath())
	issueAbsDir, err := filepath.Abs(issueRelDir)
	if err != nil {
		p.log.Warnf("Can't abs-ify path %q: %s", issueRelDir, err)
		return true
	}

	for _, absArgDir := range p.absArgsDirs {
		if absArgDir == issueAbsDir {
			// we must not skip issues if they are from explicitly set dirs
			// even if they match skip patterns
			return true
		}
	}

	// We use issueRelDir for matching: it's the relative to the current
	// work dir path of directory of source file with the issue. It can lead
	// to unexpected behavior if we're analyzing files out of current work dir.
	// The alternative solution is to find relative to args path, but it has
	// disadvantages (https://github.com/golangci/golangci-lint/pull/313).

	for _, pattern := range p.patterns {
		if pattern.MatchString(issueRelDir) {
			ps := pattern.String()
			p.skippedDirs[ps] = append(p.skippedDirs[ps], issueRelDir)
			return false
		}
	}

	return true
}

func (p SkipDirs) Finish() {
	for pattern, dirs := range p.skippedDirs {
		p.log.Infof("Skipped by pattern %s dirs: %s", pattern, dirs)
	}
}