package capi

import (
	"context"
	"net/http"
	"testing"
	"time"

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/pkg/httpmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetJobRequiresRepoAndJobID(t *testing.T) {
	client := &CAPIClient{}
	_, err := client.GetJob(context.Background(), "", "", "only-job-id")
	assert.EqualError(t, err, "owner, repo, and jobID are required")
	_, err = client.GetJob(context.Background(), "", "only-repo", "")
	assert.EqualError(t, err, "owner, repo, and jobID are required")
	_, err = client.GetJob(context.Background(), "only-owner", "", "")
	assert.EqualError(t, err, "owner, repo, and jobID are required")
	_, err = client.GetJob(context.Background(), "", "", "")
	assert.EqualError(t, err, "owner, repo, and jobID are required")
}

func TestGetJob(t *testing.T) {
	sampleDateString := "2025-08-29T00:00:00Z"
	sampleDate, err := time.Parse(time.RFC3339, sampleDateString)
	require.NoError(t, err)

	tests := []struct {
		name      string
		httpStubs func(*testing.T, *httpmock.Registry)
		wantErr   string
		wantOut   *Job
	}{
		{
			name: "job without PR",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("GET", "agents/swe/v1/jobs/OWNER/REPO/job123"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(200, heredoc.Docf(`
						{
							"job_id": "job123",
							"session_id": "sess1",
							"problem_statement": "Do the thing",
							"event_type": "foo",
							"content_filter_mode": "foo",
							"status": "foo",
							"result": "foo",
							"actor": {
								"id": 1,
								"login": "octocat"
							},
							"created_at": "%[1]s",
							"updated_at": "%[1]s"
						}`,
						sampleDateString,
					)),
				)
			},
			wantOut: &Job{
				ID:                "job123",
				SessionID:         "sess1",
				ProblemStatement:  "Do the thing",
				EventType:         "foo",
				ContentFilterMode: "foo",
				Status:            "foo",
				Result:            "foo",
				Actor: &JobActor{
					ID:    1,
					Login: "octocat",
				},
				CreatedAt: sampleDate,
				UpdatedAt: sampleDate,
			},
		},
		{
			name: "job with PR",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("GET", "agents/swe/v1/jobs/OWNER/REPO/job123"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(200, heredoc.Docf(`
						{
							"job_id": "job123",
							"session_id": "sess1",
							"problem_statement": "Do the thing",
							"event_type": "foo",
							"content_filter_mode": "foo",
							"status": "foo",
							"result": "foo",
							"actor": {
								"id": 1,
								"login": "octocat"
							},
							"created_at": "%[1]s",
							"updated_at": "%[1]s",
							"pull_request": {
								"id": 101,
								"number": 42
							}
						}`,
						sampleDateString,
					)),
				)
			},
			wantOut: &Job{
				ID:                "job123",
				SessionID:         "sess1",
				ProblemStatement:  "Do the thing",
				EventType:         "foo",
				ContentFilterMode: "foo",
				Status:            "foo",
				Result:            "foo",
				Actor: &JobActor{
					ID:    1,
					Login: "octocat",
				},
				CreatedAt: sampleDate,
				UpdatedAt: sampleDate,
				PullRequest: &JobPullRequest{
					ID:     101,
					Number: 42,
				},
			},
		},
		{
			name: "job not found",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("GET", "agents/swe/v1/jobs/OWNER/REPO/job123"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(404, `{}`),
				)
			},
			wantErr: "failed to get job: 404 Not Found",
		},
		{
			name: "API error",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("GET", "agents/swe/v1/jobs/OWNER/REPO/job123"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(500, `{}`),
				)
			},
			wantErr: "failed to get job: 500 Internal Server Error",
		},
		{
			name: "invalid JSON response",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("GET", "agents/swe/v1/jobs/OWNER/REPO/job123"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(200, ``),
				)
			},
			wantErr: "failed to decode get job response: EOF",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			if tt.httpStubs != nil {
				tt.httpStubs(t, reg)
			}
			defer reg.Verify(t)

			httpClient := &http.Client{Transport: reg}

			capiClient := NewCAPIClient(httpClient, "", "github.com", "https://api.githubcopilot.com")

			job, err := capiClient.GetJob(context.Background(), "OWNER", "REPO", "job123")

			if tt.wantErr != "" {
				require.EqualError(t, err, tt.wantErr)
				require.Nil(t, job)
				return
			}

			require.NoError(t, err)
			require.Equal(t, tt.wantOut, job)
		})
	}
}

func TestCreateJobRequiresRepoAndProblemStatement(t *testing.T) {
	client := &CAPIClient{}

	_, err := client.CreateJob(context.Background(), "", "only-repo", "", "", "")
	assert.EqualError(t, err, "owner and repo are required")
	_, err = client.CreateJob(context.Background(), "only-owner", "", "", "", "")
	assert.EqualError(t, err, "owner and repo are required")
	_, err = client.CreateJob(context.Background(), "", "", "", "", "")
	assert.EqualError(t, err, "owner and repo are required")

	_, err = client.CreateJob(context.Background(), "owner", "repo", "", "", "")
	assert.EqualError(t, err, "problem statement is required")
}

func TestCreateJob(t *testing.T) {
	sampleDateString := "2025-08-29T00:00:00Z"
	sampleDate, err := time.Parse(time.RFC3339, sampleDateString)
	require.NoError(t, err)

	tests := []struct {
		name        string
		baseBranch  string
		customAgent string
		httpStubs   func(*testing.T, *httpmock.Registry)
		wantErr     string
		wantOut     *Job
	}{
		{
			name: "success",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.RESTPayload(201,
						heredoc.Docf(`
							{
								"job_id": "job123",
								"session_id": "sess1",
								"problem_statement": "Do the thing",
								"event_type": "foo",
								"content_filter_mode": "foo",
								"status": "foo",
								"result": "foo",
								"actor": {
									"id": 1,
									"login": "octocat"
								},
								"created_at": "%[1]s",
								"updated_at": "%[1]s"
							}
						`, sampleDateString),
						func(payload map[string]interface{}) {
							assert.Equal(t, "Do the thing", payload["problem_statement"])
							assert.Equal(t, "gh_cli", payload["event_type"])
						},
					),
				)
			},
			wantOut: &Job{
				ID:                "job123",
				SessionID:         "sess1",
				ProblemStatement:  "Do the thing",
				EventType:         "foo",
				ContentFilterMode: "foo",
				Status:            "foo",
				Result:            "foo",
				Actor: &JobActor{
					ID:    1,
					Login: "octocat",
				},
				CreatedAt: sampleDate,
				UpdatedAt: sampleDate,
			},
		},
		{
			name:       "success with base branch",
			baseBranch: "some-branch",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.RESTPayload(201,
						heredoc.Docf(`
							{
								"job_id": "job123",
								"session_id": "sess1",
								"problem_statement": "Do the thing",
								"event_type": "foo",
								"content_filter_mode": "foo",
								"status": "foo",
								"result": "foo",
								"actor": {
									"id": 1,
									"login": "octocat"
								},
								"created_at": "%[1]s",
								"updated_at": "%[1]s"
							}
						`, sampleDateString),
						func(payload map[string]interface{}) {
							assert.Equal(t, "Do the thing", payload["problem_statement"])
							assert.Equal(t, "gh_cli", payload["event_type"])
							assert.Equal(t, "refs/heads/some-branch", payload["pull_request"].(map[string]interface{})["base_ref"])
						},
					),
				)
			},
			wantOut: &Job{
				ID:                "job123",
				SessionID:         "sess1",
				ProblemStatement:  "Do the thing",
				EventType:         "foo",
				ContentFilterMode: "foo",
				Status:            "foo",
				Result:            "foo",
				Actor: &JobActor{
					ID:    1,
					Login: "octocat",
				},
				CreatedAt: sampleDate,
				UpdatedAt: sampleDate,
			},
		},
		{
			name:        "Success with custom agent",
			customAgent: "my-custom-agent",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.RESTPayload(201,
						heredoc.Docf(`
							{
								"job_id": "job123",
								"session_id": "sess1",
								"problem_statement": "Do the thing",
								"custom_agent": "my-custom-agent",
								"event_type": "foo",
								"content_filter_mode": "foo",
								"status": "foo",
								"result": "foo",
								"actor": {
									"id": 1,
									"login": "octocat"
								},
								"created_at": "%[1]s",
								"updated_at": "%[1]s"
							}
						`, sampleDateString),
						func(payload map[string]interface{}) {
							assert.Equal(t, "Do the thing", payload["problem_statement"])
							assert.Equal(t, "gh_cli", payload["event_type"])
							assert.Equal(t, "my-custom-agent", payload["custom_agent"])
						},
					),
				)
			},
			wantOut: &Job{
				ID:                "job123",
				SessionID:         "sess1",
				ProblemStatement:  "Do the thing",
				CustomAgent:       "my-custom-agent",
				EventType:         "foo",
				ContentFilterMode: "foo",
				Status:            "foo",
				Result:            "foo",
				Actor: &JobActor{
					ID:    1,
					Login: "octocat",
				},
				CreatedAt: sampleDate,
				UpdatedAt: sampleDate,
			},
		},
		{
			name: "API error, included in response body",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(500, heredoc.Doc(`{
						"error": {
							"message": "some error"
						}
					}`)),
				)
			},
			wantErr: "failed to create job: 500 Internal Server Error: some error",
		},
		{
			name: "API error",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(500, `{}`),
				)
			},
			wantErr: "failed to create job: 500 Internal Server Error: ",
		},
		{
			name: "invalid JSON response, non-HTTP 200",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(401, `Unauthorized`),
				)
			},
			wantErr: "failed to create job: 401 Unauthorized",
		},
		{
			name: "invalid JSON response, HTTP 200",
			httpStubs: func(t *testing.T, reg *httpmock.Registry) {
				reg.Register(
					httpmock.WithHost(httpmock.REST("POST", "agents/swe/v1/jobs/OWNER/REPO"), "api.githubcopilot.com"),
					httpmock.StatusStringResponse(200, ``),
				)
			},
			wantErr: "failed to decode create job response: EOF",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reg := &httpmock.Registry{}
			if tt.httpStubs != nil {
				tt.httpStubs(t, reg)
			}
			defer reg.Verify(t)

			httpClient := &http.Client{Transport: reg}

			capiClient := NewCAPIClient(httpClient, "", "github.com", "https://api.githubcopilot.com")

			job, err := capiClient.CreateJob(context.Background(), "OWNER", "REPO", "Do the thing", tt.baseBranch, tt.customAgent)

			if tt.wantErr != "" {
				require.EqualError(t, err, tt.wantErr)
				require.Nil(t, job)
				return
			}

			require.NoError(t, err)
			require.Equal(t, tt.wantOut, job)
		})
	}
}
