package view

import (
	"io"
	"net/http"
	"strings"
	"testing"

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

func TestNewCmdView(t *testing.T) {
	tests := []struct {
		name    string
		args    []string
		wantErr bool
	}{
		{name: "view with org and id", args: []string{"rg-1", "--org", "my-org"}, wantErr: false},
		{name: "view with json", args: []string{"rg-1", "--org", "my-org", "--json"}, wantErr: false},
		{name: "missing org", args: []string{"rg-1"}, wantErr: true},
		{name: "missing id", args: []string{}, wantErr: true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := cmdutil.TestFactory()
			cmd := NewCmdView(f, func(opts *ViewOptions) error {
				return nil
			})
			cmd.SetArgs(tt.args)

			err := cmd.Execute()
			if (err != nil) != tt.wantErr {
				t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestViewRunJSONOutput(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, out, _ := iostreams.Test()
	opts := &ViewOptions{
		IO: io,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{
				Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
					return viewTestResponse(http.StatusOK, `{"runner_group_id":"1","runner_group_name":"prod","share_all":true,"share_all_public_repos":false,"explicit_shared_repo_count":3,"created_at":1700000000000,"updated_at":1700000100000}`), nil
				}),
			}, nil
		},
		Org:           "my-org",
		RunnerGroupID: "1",
		JSON:          true,
	}

	if err := viewRun(opts); err != nil {
		t.Fatalf("viewRun() error = %v", err)
	}

	got := strings.TrimSpace(out.String())
	if !strings.Contains(got, `"runner_group_id":"1"`) {
		t.Fatalf("JSON output = %q, missing runner_group_id", got)
	}
	if !strings.Contains(got, `"runner_group_name":"prod"`) {
		t.Fatalf("JSON output = %q, missing runner_group_name", got)
	}
	if !strings.Contains(got, `"share_all":true`) {
		t.Fatalf("JSON output = %q, missing share_all", got)
	}
}

func TestViewRunTextOutput(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, out, _ := iostreams.Test()
	opts := &ViewOptions{
		IO: io,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{
				Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
					return viewTestResponse(http.StatusOK, `{"runner_group_id":"1","runner_group_name":"prod","share_all":true,"share_all_public_repos":false,"explicit_shared_repo_count":3,"created_at":1700000000000,"updated_at":1700000100000}`), nil
				}),
			}, nil
		},
		Org:           "my-org",
		RunnerGroupID: "1",
	}

	if err := viewRun(opts); err != nil {
		t.Fatalf("viewRun() error = %v", err)
	}

	got := out.String()
	if !strings.Contains(got, "prod") {
		t.Fatalf("text output = %q, missing group name", got)
	}
	if !strings.Contains(got, "Share All:") && !strings.Contains(got, "yes") {
		t.Fatalf("text output = %q, missing share status", got)
	}
	if !strings.Contains(got, "Shared Repos:") {
		t.Fatalf("text output = %q, missing shared repo count", got)
	}
}

func TestViewRunBuildsV8Path(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, _, _ := iostreams.Test()
	var gotPath string
	opts := &ViewOptions{
		IO: io,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{
				Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
					gotPath = req.URL.Path
					return viewTestResponse(http.StatusOK, `{"runner_group_id":"1","runner_group_name":"prod"}`), nil
				}),
			}, nil
		},
		Org:           "my-org",
		RunnerGroupID: "rg-1",
	}

	if err := viewRun(opts); err != nil {
		t.Fatalf("viewRun() error = %v", err)
	}

	want := "/api/v8/orgs/my-org/actions/runner-groups/rg-1"
	if gotPath != want {
		t.Fatalf("request path = %q, want %q", gotPath, want)
	}
}

func TestViewRunMissingOrg(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, _, _ := iostreams.Test()
	opts := &ViewOptions{
		IO:            io,
		HttpClient:    func() (*http.Client, error) { return &http.Client{}, nil },
		Org:           "",
		RunnerGroupID: "1",
	}

	err := viewRun(opts)
	if err == nil {
		t.Fatal("viewRun() error = nil, want error for missing --org")
	}
}

func TestViewRunMissingID(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, _, _ := iostreams.Test()
	opts := &ViewOptions{
		IO:            io,
		HttpClient:    func() (*http.Client, error) { return &http.Client{}, nil },
		Org:           "my-org",
		RunnerGroupID: "",
	}

	err := viewRun(opts)
	if err == nil {
		t.Fatal("viewRun() error = nil, want error for missing runner group id")
	}
}

func TestViewRunAPIError(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, _, _ := iostreams.Test()
	opts := &ViewOptions{
		IO: io,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{
				Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
					return viewTestResponse(http.StatusNotFound, `{"message":"not found"}`), nil
				}),
			}, nil
		},
		Org:           "my-org",
		RunnerGroupID: "nonexistent",
	}

	err := viewRun(opts)
	if err == nil {
		t.Fatal("viewRun() error = nil, want error for 404")
	}
}

func TestViewRunJSONFaithfulOutput(t *testing.T) {
	t.Setenv("GC_TOKEN", "test-token")

	io, _, out, _ := iostreams.Test()
	opts := &ViewOptions{
		IO: io,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{
				Transport: testutil.NewRoundTripFunc(func(req *http.Request) (*http.Response, error) {
					return viewTestResponse(http.StatusOK, `{"runner_group_id":"1","runner_group_name":"prod","share_all":true,"extra_field":"preserved"}`), nil
				}),
			}, nil
		},
		Org:           "my-org",
		RunnerGroupID: "1",
		JSON:          true,
	}

	if err := viewRun(opts); err != nil {
		t.Fatalf("viewRun() error = %v", err)
	}

	got := strings.TrimSpace(out.String())
	if !strings.Contains(got, "extra_field") {
		t.Fatalf("JSON output = %q, missing extra_field (not faithful)", got)
	}
}

func viewTestResponse(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)),
	}
}