package exec

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

func TestApiclientReviewAPI(t *testing.T) {
	var method, path, body string
	var resolveCalls atomic.Int32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		method, path = r.Method, r.URL.Path
		if r.Body != nil {
			b, _ := io.ReadAll(r.Body)
			body = string(b)
		}
		switch r.Method {
		case "POST": // resolve-channel
			resolveCalls.Add(1)
			var request map[string]string
			if err := json.Unmarshal([]byte(body), &request); err != nil {
				t.Fatal(err)
			}
			if request["purpose"] != "review_context" {
				t.Fatalf("resolve purpose = %q", request["purpose"])
			}
			io.WriteString(w, `{"scopeId":"th-1"}`)
		case "GET": // list review-contexts
			io.WriteString(w, `{"contexts":[{"id":"rcx_1","kind":"git_repo","rootPath":"/r","displayName":"Repository"}]}`)
		case "PUT": // add review-context (singular)
			io.WriteString(w, `{"context":{"id":"rcx_2","kind":"git_repo","rootPath":"/r","displayName":"Repository"}}`)
		case "DELETE":
			w.WriteHeader(http.StatusNoContent)
		}
	}))
	defer srv.Close()
	api := reviewRemote{client: &apiclient.Client{
		ServerURL: srv.URL, AgentID: "ag", AuthToken: "tok",
		RouteWorkspaceID: "ws", HTTPClient: srv.Client(),
		TargetResolver: apiclient.NewTargetResolver(),
	}}

	t.Run("list resolves target then GET", func(t *testing.T) {
		got, err := api.list(context.Background(), "#chan:abc")
		if err != nil {
			t.Fatal(err)
		}
		if len(got) != 1 || got[0].ID != "rcx_1" {
			t.Errorf("result: %+v", got)
		}
		if got[0].Name != "Repository" {
			t.Errorf("display name mapping: %+v", got[0])
		}
	})
	t.Run("add resolves then PUT with field mapping", func(t *testing.T) {
		body = ""
		rc, err := api.add(context.Background(), "#chan:abc", reviewAddRequest{
			Path: "/r", Name: "Repository", PRURL: "https://example.com/pr/1",
		})
		if err != nil {
			t.Fatal(err)
		}
		if rc.ID != "rcx_2" {
			t.Errorf("result: %+v", rc)
		}
		if !strings.Contains(body, `"rootPath":"/r"`) || !strings.Contains(body, `"kind":"git_repo"`) {
			t.Errorf("body: %s", body)
		}
		if !strings.Contains(body, `"displayName":"Repository"`) || !strings.Contains(body, `"prUrl":"https://example.com/pr/1"`) {
			t.Errorf("body missing canonical review fields: %s", body)
		}
		if strings.Contains(body, `"name"`) || strings.Contains(body, `"display_name"`) || strings.Contains(body, `"pr_url"`) {
			t.Errorf("body leaked legacy review fields: %s", body)
		}
	})
	t.Run("task scope skips target resolution and sends strict scope", func(t *testing.T) {
		body = ""
		got, err := api.list(context.Background(), reviewScope{taskScopeID: "tsk_123"})
		if err != nil {
			t.Fatal(err)
		}
		if len(got) != 1 || !strings.Contains(path, "/tasks/tsk_123/review-contexts") {
			t.Errorf("task list request: path=%s contexts=%+v", path, got)
		}
		_, err = api.add(context.Background(), reviewScope{taskScopeID: "tsk_123"}, reviewAddRequest{Path: "/r"})
		if err != nil {
			t.Fatal(err)
		}
		if !strings.Contains(path, "/tasks/tsk_123/review-context") || !strings.Contains(body, `"taskScope":{"kind":"task","scopeId":"tsk_123"}`) {
			t.Errorf("task add request: path=%s body=%s", path, body)
		}
	})
	t.Run("delete DELETE by id", func(t *testing.T) {
		method, path = "", ""
		if err := api.delete(context.Background(), "rcx_9"); err != nil {
			t.Fatal(err)
		}
		if method != http.MethodDelete || !strings.Contains(path, "/review-contexts/rcx_9") {
			t.Errorf("req: %s %s", method, path)
		}
	})
	if resolveCalls.Load() != 1 {
		t.Fatalf("resolve calls = %d, want 1 shared by list and add", resolveCalls.Load())
	}
}

func TestReviewRemoteInvalidatesStaleScopeWithoutReplayingRequest(t *testing.T) {
	var resolveCalls atomic.Int32
	var listCalls atomic.Int32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPost:
			scopeID := fmt.Sprintf("th-%d", resolveCalls.Add(1))
			_, _ = fmt.Fprintf(w, `{"scopeId":%q}`, scopeID)
		case http.MethodGet:
			if listCalls.Add(1) == 1 {
				w.WriteHeader(http.StatusGone)
				_, _ = io.WriteString(w, `{"error":"scope gone"}`)
				return
			}
			_, _ = io.WriteString(w, `{"contexts":[]}`)
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer srv.Close()
	remote := reviewRemote{client: &apiclient.Client{
		ServerURL: srv.URL, AgentID: "ag", AuthToken: "tok",
		RouteWorkspaceID: "ws", HTTPClient: srv.Client(),
		TargetResolver: apiclient.NewTargetResolver(),
	}}

	_, err := remote.list(context.Background(), "#chan:deadbeef")
	if err == nil {
		t.Fatal("expected stale scope error")
	}
	if listCalls.Load() != 1 {
		t.Fatalf("list calls = %d, want no automatic replay", listCalls.Load())
	}

	contexts, err := remote.list(context.Background(), "#chan:deadbeef")
	if err != nil {
		t.Fatal(err)
	}
	if len(contexts) != 0 {
		t.Fatalf("contexts = %+v, want empty", contexts)
	}
	if resolveCalls.Load() != 2 {
		t.Fatalf("resolve calls = %d, want stale entry reloaded", resolveCalls.Load())
	}
	if listCalls.Load() != 2 {
		t.Fatalf("list calls = %d", listCalls.Load())
	}
}
