package github

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

	"github.com/github/github-mcp-server/internal/githubv4mock"
	"github.com/github/github-mcp-server/pkg/translations"
	"github.com/shurcooL/githubv4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

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

	toolDef := ProjectsWrite(translations.NullTranslationHelper)

	t.Run("success user project", func(t *testing.T) {
		t.Parallel()

		mockedClient := githubv4mock.NewMockedHTTPClient(
			githubv4mock.NewQueryMatcher(
				struct {
					User struct {
						ID string
					} `graphql:"user(login: $login)"`
				}{},
				map[string]any{
					"login": githubv4.String("octocat"),
				},
				githubv4mock.DataResponse(map[string]any{
					"user": map[string]any{
						"id": "U_octocat",
					},
				}),
			),
			githubv4mock.NewMutationMatcher(
				struct {
					CreateProjectV2 struct {
						ProjectV2 struct {
							ID     string
							Number int
							Title  string
							URL    string
						}
					} `graphql:"createProjectV2(input: $input)"`
				}{},
				githubv4.CreateProjectV2Input{
					OwnerID: githubv4.ID("U_octocat"),
					Title:   githubv4.String("New Project"),
				},
				nil,
				githubv4mock.DataResponse(map[string]any{
					"createProjectV2": map[string]any{
						"projectV2": map[string]any{
							"id":     "PVT_project123",
							"number": 1,
							"title":  "New Project",
							"url":    "https://github.com/users/octocat/projects/1",
						},
					},
				}),
			),
		)

		deps := BaseDeps{
			GQLClient: githubv4.NewClient(mockedClient),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method":     "create_project",
			"owner":      "octocat",
			"owner_type": "user",
			"title":      "New Project",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.False(t, result.IsError)

		textContent := getTextResult(t, result)
		var response map[string]any
		err = json.Unmarshal([]byte(textContent.Text), &response)
		require.NoError(t, err)
		assert.Equal(t, "PVT_project123", response["id"])
		assert.Equal(t, float64(1), response["number"])
		assert.Equal(t, "New Project", response["title"])
		assert.Equal(t, "https://github.com/users/octocat/projects/1", response["url"])
	})

	t.Run("missing owner_type returns error", func(t *testing.T) {
		t.Parallel()

		deps := BaseDeps{
			GQLClient: githubv4.NewClient(githubv4mock.NewMockedHTTPClient()),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method": "create_project",
			"owner":  "octocat",
			"title":  "New Project",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.True(t, result.IsError)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "owner_type is required")
	})

	t.Run("invalid owner_type returns error", func(t *testing.T) {
		t.Parallel()

		deps := BaseDeps{
			GQLClient: githubv4.NewClient(githubv4mock.NewMockedHTTPClient()),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method":     "create_project",
			"owner":      "octocat",
			"owner_type": "invalid",
			"title":      "New Project",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.True(t, result.IsError)

		textContent := getTextResult(t, result)
		assert.Contains(t, textContent.Text, "invalid owner_type")
		assert.Contains(t, textContent.Text, "must be")
	})
}

// resolveProjectNodeIDOrgMatcher returns a GraphQL query matcher for resolving
// an org project node ID via resolveProjectNodeID.
func resolveProjectNodeIDOrgMatcher(owner string, projectNumber int, nodeID string) githubv4mock.Matcher {
	return githubv4mock.NewQueryMatcher(
		struct {
			Organization struct {
				ProjectV2 struct {
					ID githubv4.ID
				} `graphql:"projectV2(number: $projectNumber)"`
			} `graphql:"organization(login: $owner)"`
		}{},
		map[string]any{
			"owner":         githubv4.String(owner),
			"projectNumber": githubv4.Int(int32(projectNumber)), //nolint:gosec // test constant
		},
		githubv4mock.DataResponse(map[string]any{
			"organization": map[string]any{
				"projectV2": map[string]any{
					"id": nodeID,
				},
			},
		}),
	)
}

func createFieldMatcher() githubv4mock.Matcher {
	return githubv4mock.NewMutationMatcher(
		struct {
			CreateProjectV2Field struct {
				ProjectV2Field struct {
					ProjectV2IterationField struct {
						ID   string
						Name string
					} `graphql:"... on ProjectV2IterationField"`
				} `graphql:"projectV2Field"`
			} `graphql:"createProjectV2Field(input: $input)"`
		}{},
		githubv4.CreateProjectV2FieldInput{
			ProjectID: githubv4.ID("PVT_project1"),
			DataType:  githubv4.ProjectV2CustomFieldType("ITERATION"),
			Name:      githubv4.String("Sprint"),
		},
		nil,
		githubv4mock.DataResponse(map[string]any{
			"createProjectV2Field": map[string]any{
				"projectV2Field": map[string]any{
					"id":   "PVTIF_field1",
					"name": "Sprint",
				},
			},
		}),
	)
}

func updateFieldIterationResponse() githubv4mock.GQLResponse {
	return githubv4mock.DataResponse(map[string]any{
		"updateProjectV2Field": map[string]any{
			"projectV2Field": map[string]any{
				"id":   "PVTIF_field1",
				"name": "Sprint",
				"configuration": map[string]any{
					"iterations": []any{
						map[string]any{
							"id":        "PVTI_iter1",
							"title":     "Sprint 1",
							"startDate": "2025-01-20",
							"duration":  7,
						},
					},
				},
			},
		},
	})
}

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

	toolDef := ProjectsWrite(translations.NullTranslationHelper)

	t.Run("success with iterations", func(t *testing.T) {
		t.Parallel()

		mockGQLClient := githubv4mock.NewMockedHTTPClient(
			resolveProjectNodeIDOrgMatcher("octo-org", 1, "PVT_project1"),
			createFieldMatcher(),
			githubv4mock.NewMutationMatcher(
				struct {
					UpdateProjectV2Field struct {
						ProjectV2Field struct {
							ProjectV2IterationField struct {
								ID            string
								Name          string
								Configuration struct {
									Iterations []struct {
										ID        string
										Title     string
										StartDate string
										Duration  int
									}
								}
							} `graphql:"... on ProjectV2IterationField"`
						} `graphql:"projectV2Field"`
					} `graphql:"updateProjectV2Field(input: $input)"`
				}{},
				UpdateProjectV2FieldInput{
					FieldID: githubv4.ID("PVTIF_field1"),
					IterationConfiguration: &ProjectV2IterationFieldConfigurationInput{
						Duration:  githubv4.Int(7),
						StartDate: githubv4.Date{Time: time.Date(2025, 1, 20, 0, 0, 0, 0, time.UTC)},
						Iterations: []ProjectV2IterationFieldIterationInput{
							{
								Title:     githubv4.String("Sprint 1"),
								StartDate: githubv4.Date{Time: time.Date(2025, 1, 20, 0, 0, 0, 0, time.UTC)},
								Duration:  githubv4.Int(7),
							},
						},
					},
				},
				nil,
				updateFieldIterationResponse(),
			),
		)

		deps := BaseDeps{
			GQLClient: githubv4.NewClient(mockGQLClient),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method":             "create_iteration_field",
			"owner":              "octo-org",
			"owner_type":         "org",
			"project_number":     float64(1),
			"field_name":         "Sprint",
			"iteration_duration": float64(7),
			"start_date":         "2025-01-20",
			"iterations": []any{
				map[string]any{
					"title":      "Sprint 1",
					"start_date": "2025-01-20",
					"duration":   float64(7),
				},
			},
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.False(t, result.IsError)

		textContent := getTextResult(t, result)
		var response map[string]any
		err = json.Unmarshal([]byte(textContent.Text), &response)
		require.NoError(t, err)
		assert.Equal(t, "PVTIF_field1", response["id"])
	})

	t.Run("success without iterations", func(t *testing.T) {
		t.Parallel()

		mockGQLClient := githubv4mock.NewMockedHTTPClient(
			resolveProjectNodeIDOrgMatcher("octo-org", 1, "PVT_project1"),
			createFieldMatcher(),
			githubv4mock.NewMutationMatcher(
				struct {
					UpdateProjectV2Field struct {
						ProjectV2Field struct {
							ProjectV2IterationField struct {
								ID            string
								Name          string
								Configuration struct {
									Iterations []struct {
										ID        string
										Title     string
										StartDate string
										Duration  int
									}
								}
							} `graphql:"... on ProjectV2IterationField"`
						} `graphql:"projectV2Field"`
					} `graphql:"updateProjectV2Field(input: $input)"`
				}{},
				UpdateProjectV2FieldInput{
					FieldID: githubv4.ID("PVTIF_field1"),
					IterationConfiguration: &ProjectV2IterationFieldConfigurationInput{
						Duration:   githubv4.Int(7),
						StartDate:  githubv4.Date{Time: time.Date(2025, 1, 20, 0, 0, 0, 0, time.UTC)},
						Iterations: []ProjectV2IterationFieldIterationInput{},
					},
				},
				nil,
				githubv4mock.DataResponse(map[string]any{
					"updateProjectV2Field": map[string]any{
						"projectV2Field": map[string]any{
							"id":   "PVTIF_field1",
							"name": "Sprint",
							"configuration": map[string]any{
								"iterations": []any{},
							},
						},
					},
				}),
			),
		)

		deps := BaseDeps{
			GQLClient: githubv4.NewClient(mockGQLClient),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method":             "create_iteration_field",
			"owner":              "octo-org",
			"owner_type":         "org",
			"project_number":     float64(1),
			"field_name":         "Sprint",
			"iteration_duration": float64(7),
			"start_date":         "2025-01-20",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.False(t, result.IsError)

		textContent := getTextResult(t, result)
		var response map[string]any
		err = json.Unmarshal([]byte(textContent.Text), &response)
		require.NoError(t, err)
		assert.Equal(t, "PVTIF_field1", response["id"])
	})

	t.Run("success with auto-detected owner_type", func(t *testing.T) {
		t.Parallel()

		// detectOwnerType uses REST to probe user first, then org
		mockRESTClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
			GetUsersProjectsV2ByUsernameByProject: mockResponse(t, http.StatusNotFound, nil),
			GetOrgsProjectsV2ByProject: mockResponse(t, http.StatusOK, map[string]any{
				"id":      1,
				"node_id": "PVT_project1",
				"title":   "Org Project",
			}),
		})

		mockGQLClient := githubv4mock.NewMockedHTTPClient(
			resolveProjectNodeIDOrgMatcher("octo-org", 1, "PVT_project1"),
			createFieldMatcher(),
			githubv4mock.NewMutationMatcher(
				struct {
					UpdateProjectV2Field struct {
						ProjectV2Field struct {
							ProjectV2IterationField struct {
								ID            string
								Name          string
								Configuration struct {
									Iterations []struct {
										ID        string
										Title     string
										StartDate string
										Duration  int
									}
								}
							} `graphql:"... on ProjectV2IterationField"`
						} `graphql:"projectV2Field"`
					} `graphql:"updateProjectV2Field(input: $input)"`
				}{},
				UpdateProjectV2FieldInput{
					FieldID: githubv4.ID("PVTIF_field1"),
					IterationConfiguration: &ProjectV2IterationFieldConfigurationInput{
						Duration:   githubv4.Int(14),
						StartDate:  githubv4.Date{Time: time.Date(2025, 2, 1, 0, 0, 0, 0, time.UTC)},
						Iterations: []ProjectV2IterationFieldIterationInput{},
					},
				},
				nil,
				githubv4mock.DataResponse(map[string]any{
					"updateProjectV2Field": map[string]any{
						"projectV2Field": map[string]any{
							"id":   "PVTIF_field1",
							"name": "Sprint",
							"configuration": map[string]any{
								"iterations": []any{},
							},
						},
					},
				}),
			),
		)

		deps := BaseDeps{
			Client:    mustNewGHClient(t, mockRESTClient),
			GQLClient: githubv4.NewClient(mockGQLClient),
			Obsv:      stubExporters(),
		}
		handler := toolDef.Handler(deps)
		request := createMCPRequest(map[string]any{
			"method":             "create_iteration_field",
			"owner":              "octo-org",
			"project_number":     float64(1),
			"field_name":         "Sprint",
			"iteration_duration": float64(14),
			"start_date":         "2025-02-01",
		})
		result, err := handler(ContextWithDeps(context.Background(), deps), &request)

		require.NoError(t, err)
		require.False(t, result.IsError)

		textContent := getTextResult(t, result)
		var response map[string]any
		err = json.Unmarshal([]byte(textContent.Text), &response)
		require.NoError(t, err)
		assert.Equal(t, "PVTIF_field1", response["id"])
	})
}
