package ignorefiles

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/kovidgoyal/kitty/tools/utils"
)

var _ = fmt.Print

func TestGitignore(t *testing.T) {
	for line, expected := range map[string]struct {
		skipped, negated, only_dirs bool
		parts                       []string
	}{
		"":           {skipped: true},
		" ":          {skipped: true},
		"  ":         {skipped: true},
		"/":          {skipped: true},
		"//":         {skipped: true},
		"# abc":      {skipped: true},
		`\!moose \ `: {parts: []string{`!moose  `}},
		`\#m\oose  `: {parts: []string{`#m\oose`}},
	} {
		p, skipped := CompileGitIgnoreLine(line)
		if skipped != expected.skipped {
			t.Fatalf("skipped: %v != %v for line: %s", expected.skipped, skipped, line)
		}
		if !skipped {
			if p.negated != expected.negated {
				t.Fatalf("negated: %v != %v for line: %s", expected.negated, p.negated, line)
			}
			if p.only_dirs != expected.only_dirs {
				t.Fatalf("only_dirs: %v != %v for line: %s", expected.only_dirs, p.only_dirs, line)
			}
			if diff := cmp.Diff(expected.parts, p.parts); diff != "" {
				t.Fatalf("parts not equal for line: %s\n%s", line, diff)
			}
		}
	}
	type ptest struct {
		path     string
		expected bool
	}
	for _, x := range []struct {
		line  string
		tests []ptest
	}{
		{"foo", []ptest{
			{"foo", true}, {"x/foo", true}, {"foo/x", true},
		}},
		{"/foo", []ptest{
			{"foo", true}, {"x/foo", false},
		}},
		{"doc/frotz/", []ptest{
			{"doc/frotz/", true}, {"a/doc/frotz/", false}, {"doc/frotz", false},
		}},
		{"frotz/", []ptest{
			{"frotz/", true}, {"a/doc/frotz/", true}, {"doc/frotz", false}, {"frotz/", true},
		}},
		{"foo.*", []ptest{
			{"foo.txt", true}, {"foo", false}, {"a/foo.x", true}, {"foo.", true},
		}},

		{"**", []ptest{
			{"foo", true}, {"x/foo", true}, {"foo/x", true},
		}},

		{"**/foo", []ptest{
			{"foo", true}, {"x/foo", true}, {"foo/x", false},
		}},
		{"**/foo/bar", []ptest{
			{"foo", false}, {"x/foo", false}, {"foo/bar", true}, {"a/foo/bar", true}, {"foo/bar/a", false},
		}},
		{"foo/**", []ptest{
			{"foo", false}, {"x/foo", false}, {"foo/bar", true}, {"foo/bar/a", true}, {"foo/bar/a/", true},
		}},
		{"a/**/b", []ptest{
			{"a/b", true}, {"a/x/b", true}, {"a/x/y/b", true}, {"x/a/b", false}, {"a/b/x", false},
		}},
		{"a/**/b/**/c", []ptest{
			{"a/b/c", true}, {"a/x/b/c", true}, {"a/x/y/b/m/n/c", true},
		}},
	} {
		p, skipped := CompileGitIgnoreLine(x.line)
		if skipped {
			t.Fatalf("Unexpectedly failed to compile: %#v", x.line)
		}
		for _, test := range x.tests {
			path := strings.TrimRight(test.path, "/")
			ftype := utils.IfElse(len(path) < len(test.path), fs.ModeDir, 0)
			if actual := p.Match(path, ftype); actual != test.expected {
				t.Fatalf("matched: %v != %v for pattern: %#v and path: %#v", test.expected, actual, x.line, test.path)
			}
		}
	}
	for text, tests := range map[string]map[string]bool{
		``: {"foo": false},
		`
# exclude everything except directory foo/bar
/*
!/foo
/foo/*
!/foo/bar`: {
			"a": true, "foo": false, "foo/x": true, "foo/bar": false, "foo/bar/": false,
		},
		`
**/foo
bar `: {
			"foo": true, "baz/foo": true, "bar": true, "baz/bar": true, "a": false,
		},
		`/*.c`: {"a.c": true, "b/a.c": false},
		`
**/external/**/*.json
**/external/**/.*ignore
**/external/foobar/*.css`: {
			"external/foobar/angular.foo.css": true, "external/barfoo/.gitignore": true, "external/barfoo/.bower.json": true,
		},
		"abc/def\r\nxyz": {"abc/def": true, "a/xyz": true},
		`/**/foo`:        {"foo": true, "foo/": true, "a/b/foo": true, "fooo": false, "ofoo": false},
		"/.js":           {".js": true, ".js/": true, ".js/a": true, ".jsa": false},
		"*.js":           {".js": true, ".js/": true, ".js/a": true, "a.js/a": true, "a.js/a.js": true, ".jsa": false, "a.jsa": false},
		"foo/**/":        {"foo/": false, "foo": false, "foo/abc/": true, "foo/a/b/c/": true, "foo/a": false},
		"foo/**/*.bar":   {"foo/": false, "abc.bar": false, "foo/abc.bar": true, "foo/a.bar/": true, "foo/x/y/z.bar": true},
		`\#abc`:          {"abc": false, "#abc": true},
		"abc\n!abc/x":    {"abc": true, "abc/x": false, "abc/y": true},
		`abc/*`:          {"abc": false, "abc/": false, "abc/x": true},
	} {
		p := NewGitignore()
		if err := p.LoadString(text); err != nil {
			t.Fatal(err)
		}
		for tpath, expected := range tests {
			path := strings.TrimRight(tpath, "/")
			ftype := utils.IfElse(len(path) < len(tpath), fs.ModeDir, 0)
			if actual, _, _ := p.IsIgnored(path, ftype); actual != expected {
				t.Fatalf("ignored: %v != %v for path: %#v and ignorefile:\n%s", expected, actual, tpath, text)
			}
		}
	}
	tdir := t.TempDir()
	gc := filepath.Join(tdir, ".gitconfig")
	os.WriteFile(gc, []byte(`
[core]
something
[else]
...
[core]
...
[core]
excludesfile = one
[core]
excludesfile = ~/global-gitignore
`), 0600)
	if ef := _get_global_gitconfig_excludesfile(gc); ef != utils.Expanduser("~/global-gitignore") {
		t.Fatalf("global gitconfig excludes file incorrect: %s != %s", utils.Expanduser("~/global-gitignore"), ef)
	}
}
