package restic

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"git.secluded.site/keld/internal/config"
)

func TestCommandsSpotChecks(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		command    string
		optionName string
		wantAlias  string
		wantRepeat bool
	}{
		{
			name:       "backup dry-run alias",
			command:    "backup",
			optionName: "dry-run",
			wantAlias:  "n",
			wantRepeat: false,
		},
		{
			name:       "backup exclude repeatable",
			command:    "backup",
			optionName: "exclude",
			wantAlias:  "e",
			wantRepeat: true,
		},
		{
			name:       "backup tag repeatable",
			command:    "backup",
			optionName: "tag",
			wantAlias:  "",
			wantRepeat: true,
		},
		{
			name:       "global repo alias",
			command:    "global",
			optionName: "repo",
			wantAlias:  "r",
			wantRepeat: false,
		},
		{
			name:       "global verbose alias",
			command:    "global",
			optionName: "verbose",
			wantAlias:  "v",
			wantRepeat: false,
		},
	}

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

			cmd, ok := Commands[tt.command]
			if !ok {
				t.Fatalf("command %q missing from Commands map", tt.command)
			}

			opt, ok := findOption(cmd.Options, tt.optionName)
			if !ok {
				t.Fatalf("option %q missing from command %q", tt.optionName, tt.command)
			}

			if opt.Alias != tt.wantAlias {
				t.Fatalf("alias mismatch for %s/%s: got %q, want %q", tt.command, tt.optionName, opt.Alias, tt.wantAlias)
			}
			if opt.Repeatable != tt.wantRepeat {
				t.Fatalf("repeatable mismatch for %s/%s: got %v, want %v", tt.command, tt.optionName, opt.Repeatable, tt.wantRepeat)
			}
		})
	}
}

func TestEveryCommandHasHelpOption(t *testing.T) {
	t.Parallel()

	commandNames := make([]string, 0, len(Commands))
	for name := range Commands {
		commandNames = append(commandNames, name)
	}
	sort.Strings(commandNames)

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

			cmd := Commands[name]
			if _, ok := findOption(cmd.Options, "help"); !ok {
				t.Fatalf("command %q has no help option", name)
			}
		})
	}
}

func TestWarnUnknownFlags(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name      string
		command   string
		flags     []config.Flag
		wantLines []string
	}{
		{
			name:    "known command and global flags are silent",
			command: "backup",
			flags: []config.Flag{
				{Name: "--repo", Value: "/repo"},
				{Name: "--tag", Value: "daily"},
				{Name: "-n"},
			},
			wantLines: nil,
		},
		{
			name:    "unknown flags emit warnings once",
			command: "backup",
			flags: []config.Flag{
				{Name: "--repo", Value: "/repo"},
				{Name: "--unknown", Value: "x"},
				{Name: "--unknown", Value: "y"},
				{Name: "-Z"},
			},
			wantLines: []string{
				`warning: unknown restic flag "--unknown" for command "backup"`,
				`warning: unknown restic flag "-Z" for command "backup"`,
			},
		},
		{
			name:    "empty command validates against global options",
			command: "",
			flags: []config.Flag{
				{Name: "--repo", Value: "/repo"},
				{Name: "--exclude", Value: "*.tmp"},
			},
			wantLines: []string{
				`warning: unknown restic flag "--exclude" for command "global"`,
			},
		},
	}

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

			var stderr bytes.Buffer
			warnUnknownFlags(&stderr, tt.command, tt.flags)

			gotLines := splitLines(strings.TrimSpace(stderr.String()))
			if !reflect.DeepEqual(gotLines, tt.wantLines) {
				t.Fatalf("warnUnknownFlags() output mismatch:\n%s", fmtDiff(gotLines, tt.wantLines))
			}
		})
	}
}

func findOption(options []Option, name string) (Option, bool) {
	for _, option := range options {
		if option.Name == name {
			return option, true
		}
	}
	return Option{}, false
}

func splitLines(s string) []string {
	if s == "" {
		return nil
	}
	return strings.Split(s, "\n")
}

func fmtDiff(got, want []string) string {
	return fmt.Sprintf("got:\n%q\nwant:\n%q", got, want)
}
