package ones_test

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/santhosh-tekuri/jsonschema/v6"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
)

func readAll(t *testing.T, r *http.Request) string {
	t.Helper()
	bodyBytes, err := io.ReadAll(r.Body)
	require.NoError(t, err)
	return string(bodyBytes)
}

// scriptedServer answers each call in order with the scripted status/body.
type scriptedServer struct {
	server   *httptest.Server
	scripts  []script
	requests []wireRequest
}

type script struct {
	status int
	body   string
}

func newScriptedServer(t *testing.T, scripts ...script) *scriptedServer {
	t.Helper()
	staged := &scriptedServer{scripts: scripts}
	index := 0
	staged.server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		current := staged.scripts[index]
		index++
		bodyBytes := readAll(t, r)
		staged.requests = append(staged.requests, wireRequest{
			method: r.Method,
			path:   r.URL.Path,
			body:   bodyBytes,
			header: r.Header.Clone(),
		})
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(current.status)
		_, _ = w.Write([]byte(current.body))
	}))
	t.Cleanup(staged.server.Close)
	return staged
}

// URL exposes the double's origin for invoke.
func (staged *scriptedServer) URL() string { return staged.server.URL }

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

	wire := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"XM9VHifj","space_uuid":"Gj7aPmA1","status":1}`},
		script{status: http.StatusOK, body: `{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","page_uuid":"8oNtjtGW","draft_uuid":"XM9VHifj","status":3}`},
	)
	result, err := invoke(t, wire, "create-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"计划","content":"内容","parent_page_uuid":"4VSnJJmk"}`)
	require.NoError(t, err)

	require.Len(t, wire.requests, 2)
	first, second := wire.requests[0], wire.requests[1]
	require.Equal(t, "POST", first.method)
	require.Equal(t, "/wiki/api/wiki/team/3pDzCwAe/space/Gj7aPmA1/drafts/add", first.path)
	require.Contains(t, first.body, `"status":1`)
	require.Contains(t, first.body, `"page_uuid":"4VSnJJmk"`)
	require.Contains(t, first.body, `"title":"计划"`)
	require.Equal(t, "POST", second.method)
	require.Equal(t, "/wiki/api/wiki/team/3pDzCwAe/space/Gj7aPmA1/draft/XM9VHifj/update", second.path)
	require.Contains(t, second.body, `"is_published":true`)

	var outcome struct {
		Draft     map[string]any `json:"draft"`
		Published bool           `json:"published"`
		Page      map[string]any `json:"page"`
	}
	require.NoError(t, json.Unmarshal(result, &outcome))
	require.Equal(t, "XM9VHifj", outcome.Draft["uuid"])
	require.True(t, outcome.Published)
	require.Equal(t, "8oNtjtGW", outcome.Page["page_uuid"])
}

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

	wire := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"TPaN4NBc","page_uuid":"CbA3y4i1","status":2}`},
		script{status: http.StatusOK, body: `{"page_uuid":"CbA3y4i1","draft_uuid":"TPaN4NBc","status":3}`},
	)
	_, err := invoke(t, wire, "update-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","page_uuid":"CbA3y4i1","title":"t2","content":"c2","is_forced":true}`)
	require.NoError(t, err)

	require.Len(t, wire.requests, 2)
	first, second := wire.requests[0], wire.requests[1]
	require.Equal(t, "/wiki/api/wiki/team/3pDzCwAe/space/Gj7aPmA1/drafts/add", first.path)
	require.Contains(t, first.body, `"status":2`)
	require.Contains(t, first.body, `"page_uuid":"CbA3y4i1"`)
	require.Equal(t, "/wiki/api/wiki/team/3pDzCwAe/space/Gj7aPmA1/draft/TPaN4NBc/update", second.path)
	require.Contains(t, second.body, `"is_forced":true`)
}

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

	wire := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"XM9VHifj","status":1}`},
		script{status: 819, body: `{"errcode":"VersionConflict"}`},
	)
	result, err := invoke(t, wire, "create-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"t","content":"c"}`)
	require.NoError(t, err)

	var outcome struct {
		Draft        map[string]any `json:"draft"`
		Published    bool           `json:"published"`
		Page         any            `json:"page"`
		PublishError string         `json:"publish_error"`
	}
	require.NoError(t, json.Unmarshal(result, &outcome))
	require.Equal(t, "XM9VHifj", outcome.Draft["uuid"])
	require.False(t, outcome.Published)
	require.Nil(t, outcome.Page)
	require.NotEmpty(t, outcome.PublishError)
}

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

	for _, rejection := range []script{
		{status: 401, body: `{}`},
		{status: 429, body: `{}`},
		{status: 500, body: `{}`},
		{status: 630, body: `{"errcode":"NotFound.Draft"}`},
	} {
		wire := newScriptedServer(t,
			script{status: http.StatusOK, body: `{"uuid":"XM9VHifj","status":1}`},
			rejection,
		)
		_, err := invoke(t, wire, "create-page",
			`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"t","content":"c"}`)
		require.Error(t, err, "status %d must propagate, not degrade to a partial envelope",
			rejection.status)
		mapped := runner.InvocationError(err)
		require.NotNil(t, mapped)
	}
}

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

	wire := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"XM9VHifj","status":1}`},
		script{status: http.StatusOK, body: `{"status":3}`},
	)
	_, err := invoke(t, wire, "create-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"t","content":"c"}`)
	mapped := runner.InvocationError(err)
	require.NotNil(t, mapped)
}

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

	wire := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"noid":true}`},
	)
	_, err := invoke(t, wire, "create-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"t","content":"c"}`)
	mapped := runner.InvocationError(err)
	require.NotNil(t, mapped)
	require.Len(t, wire.requests, 1)
}

// TestPagePublishOutcomesConformToOutputSchema validates the real handler
// outputs (success and business-conflict partial) against the declared
// create-page/update-page output schemas, so the contract can never drift
// from the envelope again.
func TestPagePublishOutcomesConformToOutputSchema(t *testing.T) {
	t.Parallel()

	compile := func(t *testing.T, resource string, schema map[string]any) *jsonschema.Schema {
		t.Helper()
		compiler := jsonschema.NewCompiler()
		compiler.DefaultDraft(jsonschema.Draft2020)
		require.NoError(t, compiler.AddResource(resource, schema))
		compiled, err := compiler.Compile(resource)
		require.NoError(t, err)
		return compiled
	}
	validate := func(t *testing.T, compiled *jsonschema.Schema, output json.RawMessage) {
		t.Helper()
		var decoded any
		require.NoError(t, json.Unmarshal(output, &decoded))
		require.NoError(t, compiled.Validate(decoded))
	}
	createSchema := compile(t, "ones-create-page-output.json", findOutputSchema(t, "create-page"))
	updateSchema := compile(t, "ones-update-page-output.json", findOutputSchema(t, "update-page"))

	success := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"XM9VHifj","status":1}`},
		script{status: http.StatusOK, body: `{"page_uuid":"8oNtjtGW","draft_uuid":"XM9VHifj","status":3}`},
	)
	result, err := invoke(t, success, "create-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","title":"t","content":"c"}`)
	require.NoError(t, err)
	validate(t, createSchema, result)

	partial := newScriptedServer(t,
		script{status: http.StatusOK, body: `{"uuid":"TPaN4NBc","status":2}`},
		script{status: 819, body: `{"errcode":"VersionConflict"}`},
	)
	result, err = invoke(t, partial, "update-page",
		`{"team_uuid":"3pDzCwAe","space_uuid":"Gj7aPmA1","page_uuid":"CbA3y4i1","title":"t","content":"c"}`)
	require.NoError(t, err)
	validate(t, updateSchema, result)
}
