package add

import (
	"encoding/json"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"

	cmdutil "gitcode.com/gitcode-cli/cli/pkg/cmdutil"
	"gitcode.com/gitcode-cli/cli/pkg/testutil"
)

func TestNewCmdAddRequiresFlags(t *testing.T) {
	cmd := NewCmdAdd(cmdutil.TestFactory(), func(*AddOptions) error { return nil })
	cmd.SetArgs(nil)
	if err := cmd.Execute(); err == nil {
		t.Fatal("expected required flag error")
	}
}

func TestAddRunReadsFileAndWritesJSON(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")
	file := t.TempDir() + "/key.pub"
	if err := os.WriteFile(file, []byte("ssh-ed25519 QUFBQQ== test@example\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	f := cmdutil.TestFactory()
	out := &strings.Builder{}
	f.IOStreams.Out = out
	opts := &AddOptions{IO: f.IOStreams, Title: "laptop", KeyFile: file, JSON: true, HttpClient: func() (*http.Client, error) {
		return &http.Client{Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
			body, _ := io.ReadAll(req.Body)
			var got map[string]string
			if err := json.Unmarshal(body, &got); err != nil {
				t.Fatal(err)
			}
			if got["title"] != "laptop" || got["key"] != "ssh-ed25519 QUFBQQ== test@example" {
				t.Fatalf("body = %s", body)
			}
			return addResponse(http.StatusOK, "{\"id\":8,\"title\":\"laptop\",\"key\":\"ssh-ed25519 QUFBQQ== test@example\"}"), nil
		})}, nil
	}}
	if err := addRun(opts); err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(out.String(), "\"id\": 8") {
		t.Fatalf("output = %s", out)
	}
}

func TestAddRunSanitizesTextOutput(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")
	file := t.TempDir() + "/key.pub"
	if err := os.WriteFile(file, []byte("ssh-ed25519 QUFBQQ== comment"), 0o600); err != nil {
		t.Fatal(err)
	}
	f := cmdutil.TestFactory()
	out := &strings.Builder{}
	f.IOStreams.Out = out
	opts := &AddOptions{IO: f.IOStreams, Title: "laptop", KeyFile: file, HttpClient: func() (*http.Client, error) {
		return &http.Client{Transport: testutil.NewRoundTripFunc(func(*http.Request) (*http.Response, error) {
			return addResponse(http.StatusOK, `{"id":8,"title":"lap\u001b[31mtop"}`), nil
		})}, nil
	}}
	if err := addRun(opts); err != nil {
		t.Fatal(err)
	}
	if strings.Contains(out.String(), "\x1b") || out.String() != "Added SSH key lap[31mtop (ID: 8).\n" {
		t.Fatalf("output = %q", out)
	}
}

func TestValidatePublicKey(t *testing.T) {
	privateKeyMarker := strings.Join([]string{"-----BEGIN OPENSSH PRIVATE", "KEY-----"}, " ")
	tests := []struct {
		name    string
		key     string
		wantErr string
	}{
		{name: "ed25519", key: "ssh-ed25519 QUFBQQ== comment"},
		{name: "ecdsa", key: "ecdsa-sha2-nistp256 QUFBQQ=="},
		{name: "private", key: privateKeyMarker, wantErr: "not a private key"},
		{name: "multiline", key: "ssh-ed25519 QUFBQQ==\nsecond", wantErr: "single OpenSSH"},
		{name: "arbitrary", key: "not-a-key", wantErr: "single OpenSSH"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validatePublicKey(tt.key)
			if tt.wantErr == "" && err != nil {
				t.Fatalf("error = %v", err)
			}
			if tt.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tt.wantErr)) {
				t.Fatalf("error = %v, want %q", err, tt.wantErr)
			}
		})
	}
}

func TestAddRunRejectsEmptyFile(t *testing.T) {
	file := t.TempDir() + "/empty.pub"
	if err := os.WriteFile(file, nil, 0o600); err != nil {
		t.Fatal(err)
	}
	f := cmdutil.TestFactory()
	err := addRun(&AddOptions{IO: f.IOStreams, Title: "empty", KeyFile: file})
	if err == nil || !strings.Contains(err.Error(), "file is empty") {
		t.Fatalf("error = %v", err)
	}
}

func TestAddRunRejectsPrivateKey(t *testing.T) {
	file := t.TempDir() + "/invalid-key"
	privateKeyMarker := strings.Join([]string{"PRIVATE", "KEY"}, " ") + " material"
	if err := os.WriteFile(file, []byte(privateKeyMarker), 0o600); err != nil {
		t.Fatal(err)
	}
	err := addRun(&AddOptions{IO: cmdutil.TestFactory().IOStreams, Title: "private", KeyFile: file})
	if err == nil || !strings.Contains(err.Error(), "not a private key") {
		t.Fatalf("error = %v", err)
	}
}

func TestAddRunRejectsMultipleLines(t *testing.T) {
	file := t.TempDir() + "/keys.pub"
	content := "ssh-ed25519 AAAA first\nssh-ed25519 BBBB second"
	if err := os.WriteFile(file, []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}
	err := addRun(&AddOptions{IO: cmdutil.TestFactory().IOStreams, Title: "multiple", KeyFile: file})
	if err == nil || !strings.Contains(err.Error(), "exactly one line") {
		t.Fatalf("error = %v", err)
	}
}

func addResponse(status int, body string) *http.Response {
	return &http.Response{StatusCode: status, Status: http.StatusText(status), Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}
}