package test

import (
	"fmt"
	"slices"
	"sort"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/golangci/golangci-lint/pkg/lint/linter"
	"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
	"github.com/golangci/golangci-lint/pkg/logutils"
	"github.com/golangci/golangci-lint/test/testshared"
)

func TestEnabledLinters(t *testing.T) {
	// require to display the message "Active x linters: [x,y]"
	t.Setenv(logutils.EnvTestRun, "1")

	cases := []struct {
		name           string
		cfg            string
		enabledLinters []string
		args           []string
		noImplicitFast bool
	}{
		{
			name: "disable govet in config",
			cfg: `
			linters:
				disable:
					- govet
			`,
			enabledLinters: getEnabledByDefaultFastLintersExcept(t, "govet"),
		},
		{
			name: "enable revive in config",
			cfg: `
			linters:
				enable:
					- revive
			`,
			enabledLinters: getEnabledByDefaultFastLintersWith(t, "revive"),
		},
		{
			name:           "disable govet in cmd",
			args:           []string{"-Dgovet"},
			enabledLinters: getEnabledByDefaultFastLintersExcept(t, "govet"),
		},
		{
			name: "enable gofmt in cmd and enable revive in config",
			args: []string{"-Egofmt"},
			cfg: `
			linters:
				enable:
					- revive
			`,
			enabledLinters: getEnabledByDefaultFastLintersWith(t, "revive", "gofmt"),
		},
		{
			name: "fast option in config",
			cfg: `
			linters:
				fast: true
			`,
			enabledLinters: getEnabledByDefaultFastLintersWith(t),
			noImplicitFast: true,
		},
		{
			name: "explicitly unset fast option in config",
			cfg: `
			linters:
				fast: false
			`,
			enabledLinters: getEnabledByDefaultLinters(t),
			noImplicitFast: true,
		},
		{
			name:           "set fast option in command-line",
			args:           []string{"--fast"},
			enabledLinters: getEnabledByDefaultFastLintersWith(t),
			noImplicitFast: true,
		},
		{
			name: "fast option in command-line has higher priority to enable",
			cfg: `
			linters:
				fast: false
			`,
			args:           []string{"--fast"},
			enabledLinters: getEnabledByDefaultFastLintersWith(t),
			noImplicitFast: true,
		},
		{
			name: "fast option in command-line has higher priority to disable",
			cfg: `
			linters:
				fast: true
			`,
			args:           []string{"--fast=false"},
			enabledLinters: getEnabledByDefaultLinters(t),
			noImplicitFast: true,
		},
		{
			name:           "fast option combined with enable and enable-all",
			args:           []string{"--enable-all", "--fast", "--enable=unused"},
			enabledLinters: getAllFastLintersWith(t, "unused"),
			noImplicitFast: true,
		},
	}

	binPath := testshared.InstallGolangciLint(t)

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			t.Parallel()

			args := []string{"--verbose"}
			if !c.noImplicitFast {
				args = append(args, "--fast")
			}

			r := testshared.NewRunnerBuilder(t).
				WithCommand("linters").
				WithArgs(args...).
				WithArgs(c.args...).
				WithConfig(c.cfg).
				WithBinPath(binPath).
				Runner().
				Run()

			sort.Strings(c.enabledLinters)

			r.ExpectOutputContains(fmt.Sprintf("Active %d linters: [%s]",
				len(c.enabledLinters), strings.Join(c.enabledLinters, " ")))
		})
	}
}

func getEnabledByDefaultFastLintersExcept(t *testing.T, except ...string) []string {
	t.Helper()

	m, err := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
	require.NoError(t, err)

	ebdl := m.GetAllEnabledByDefaultLinters()
	var ret []string
	for _, lc := range ebdl {
		if lc.IsSlowLinter() || lc.Internal {
			continue
		}

		if !slices.Contains(except, lc.Name()) {
			ret = append(ret, lc.Name())
		}
	}

	return ret
}

func getAllFastLintersWith(t *testing.T, with ...string) []string {
	t.Helper()

	ret := append([]string{}, with...)

	dbManager, err := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
	require.NoError(t, err)

	linters := dbManager.GetAllSupportedLinterConfigs()

	for _, lc := range linters {
		if lc.IsSlowLinter() || lc.Internal || (lc.IsDeprecated() && lc.Deprecation.Level > linter.DeprecationWarning) {
			continue
		}

		ret = append(ret, lc.Name())
	}

	return ret
}

func getEnabledByDefaultLinters(t *testing.T) []string {
	t.Helper()

	dbManager, err := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
	require.NoError(t, err)

	ebdl := dbManager.GetAllEnabledByDefaultLinters()
	var ret []string
	for _, lc := range ebdl {
		if lc.Internal {
			continue
		}

		ret = append(ret, lc.Name())
	}

	return ret
}

func getEnabledByDefaultFastLintersWith(t *testing.T, with ...string) []string {
	t.Helper()

	dbManager, err := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
	require.NoError(t, err)

	ebdl := dbManager.GetAllEnabledByDefaultLinters()
	ret := append([]string{}, with...)
	for _, lc := range ebdl {
		if lc.IsSlowLinter() || lc.Internal {
			continue
		}

		ret = append(ret, lc.Name())
	}

	return ret
}