package ones

import (
	"context"
	"encoding/json"
	"net/url"
	"strconv"

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

// Wiki handlers. Space/page CRUD are direct passthroughs; page create and
// update compose the real ONES draft flow: create a draft, then publish it
// with is_published=true. A publish failure keeps the draft in the output so
// the authored content is never silently lost.

func handleListSpaces(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload teamScoped
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "GET",
		WikiPrefix+"/team/"+team+"/spaces", nil, nil))
}

func handleCreateSpace(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		teamScoped
		Title       string `json:"title"`
		Description string `json:"description"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/spaces/add", nil, map[string]any{
			"title":       payload.Title,
			"description": payload.Description,
		}))
}

type spaceScoped struct {
	teamScoped
	SpaceUUID string `json:"space_uuid"`
}

func (input spaceScoped) space() (string, error) {
	spaceUUID := input.SpaceUUID
	return pathParam8("space_uuid", spaceUUID)
}

func handleGetSpace(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload spaceScoped
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "GET",
		WikiPrefix+"/team/"+team+"/space/"+space, nil, nil))
}

func handleUpdateSpace(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		Title       string `json:"title"`
		Description string `json:"description"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/space/"+space+"/update", nil, map[string]any{
			"title":       payload.Title,
			"description": payload.Description,
		}))
}

func handleDeleteSpace(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload spaceScoped
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/space/"+space+"/delete", nil, nil))
}

func handleListPages(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		Status int `json:"status"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	if payload.Status != 1 && payload.Status != 2 {
		return nil, runner.Errorf(
			runner.ErrorValidation, "status must be 1 (live) or 2 (deleted)", nil)
	}
	query := make(url.Values)
	query.Set("status", strconv.Itoa(payload.Status))
	return client.Call(ctx, credentials, request(credentials, "GET",
		WikiPrefix+"/team/"+team+"/space/"+space+"/pages", query, nil))
}

func handleGetPage(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		PageUUID string `json:"page_uuid"`
		Version  *int   `json:"version"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	page, err := pathParam8("page_uuid", payload.PageUUID)
	if err != nil {
		return nil, err
	}
	var query url.Values
	if payload.Version != nil {
		query = make(url.Values)
		query.Set("version", strconv.Itoa(*payload.Version))
	}
	return client.Call(ctx, credentials, request(credentials, "GET",
		WikiPrefix+"/team/"+team+"/space/"+space+"/page/"+page, query, nil))
}

func handleDeletePage(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		PageUUID string `json:"page_uuid"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	team, err := payload.team()
	if err != nil {
		return nil, err
	}
	space, err := payload.space()
	if err != nil {
		return nil, err
	}
	page, err := pathParam8("page_uuid", payload.PageUUID)
	if err != nil {
		return nil, err
	}
	return client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/space/"+space+"/page/"+page+"/delete", nil, nil))
}

// publishOutcome is the honest composite envelope of the draft flow. The
// published flag is explicit; a failed publish keeps the draft object and a
// stable publish_error message instead of masking partial success or losing
// the draft reference.
type publishOutcome struct {
	Draft        json.RawMessage `json:"draft"`
	Published    bool            `json:"published"`
	Page         json.RawMessage `json:"page"`
	PublishError string          `json:"publish_error,omitempty"`
}

func handleCreatePage(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		Title          string `json:"title"`
		Content        string `json:"content"`
		ParentPageUUID string `json:"parent_page_uuid"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	return publishDraft(ctx, client, credentials, payload.teamScoped,
		payload.SpaceUUID, draftCreate{
			Status:   1,
			PageUUID: payload.ParentPageUUID,
			Title:    payload.Title,
			Content:  payload.Content,
			IsForced: false,
		})
}

func handleUpdatePage(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	input json.RawMessage,
) (json.RawMessage, error) {
	var payload struct {
		spaceScoped
		PageUUID string `json:"page_uuid"`
		Title    string `json:"title"`
		Content  string `json:"content"`
		IsForced bool   `json:"is_forced"`
	}
	if err := decodeInput(input, &payload); err != nil {
		return nil, err
	}
	page, err := pathParam8("page_uuid", payload.PageUUID)
	if err != nil {
		return nil, err
	}
	return publishDraft(ctx, client, credentials, payload.teamScoped,
		payload.SpaceUUID, draftCreate{
			Status:   2,
			PageUUID: page,
			Title:    payload.Title,
			Content:  payload.Content,
			IsForced: payload.IsForced,
		})
}

// draftCreate is the drafts/add body of one draft-flow run.
type draftCreate struct {
	Status   int
	PageUUID string
	Title    string
	Content  string
	IsForced bool
}

// publishDraft runs the two-step draft flow under a fixed call budget: one
// drafts/add, one draft publish. Only a business validation or conflict
// rejection (a draft exists server-side) degrades to the partial envelope;
// auth, rate-limit, and upstream failures keep propagating as errors. A
// publish 2xx without a page uuid fails closed.
func publishDraft(
	ctx context.Context,
	client *runner.RESTClient,
	credentials runner.Credentials,
	teamInput teamScoped,
	spaceUUID string,
	draft draftCreate,
) (json.RawMessage, error) {
	team, err := teamInput.team()
	if err != nil {
		return nil, err
	}
	space, err := pathParam8("space_uuid", spaceUUID)
	if err != nil {
		return nil, err
	}
	addBody := map[string]any{
		"status":  draft.Status,
		"title":   draft.Title,
		"content": draft.Content,
	}
	if draft.PageUUID != "" {
		pageUUID, err := pathParam8("page_uuid", draft.PageUUID)
		if err != nil {
			return nil, err
		}
		addBody["page_uuid"] = pageUUID
	}
	draftRaw, err := client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/space/"+space+"/drafts/add", nil, addBody))
	if err != nil {
		return nil, err
	}
	var created struct {
		UUID string `json:"uuid"`
	}
	if err := json.Unmarshal(draftRaw, &created); err != nil || created.UUID == "" {
		return nil, runner.Errorf(
			runner.ErrorUpstreamFailure, "ones draft response was not usable", err)
	}
	draftUUID, err := pathParam8("draft_uuid", created.UUID)
	if err != nil {
		return nil, err
	}
	publishBody := map[string]any{
		"title":        draft.Title,
		"content":      draft.Content,
		"is_published": true,
	}
	if draft.IsForced {
		publishBody["is_forced"] = true
	}
	pageRaw, err := client.Call(ctx, credentials, request(credentials, "POST",
		WikiPrefix+"/team/"+team+"/space/"+space+"/draft/"+draftUUID+"/update",
		nil, publishBody))
	outcome := publishOutcome{Draft: draftRaw, Page: json.RawMessage("null")}
	if err != nil {
		if ctx.Err() != nil || runner.CodeOf(err) != runner.ErrorValidation {
			return nil, err
		}
		outcome.PublishError = err.Error()
		return json.Marshal(outcome)
	}
	var published struct {
		PageUUID string `json:"page_uuid"`
	}
	if err := json.Unmarshal(pageRaw, &published); err != nil || published.PageUUID == "" {
		return nil, runner.Errorf(
			runner.ErrorUpstreamFailure, "ones publish response was not usable", err)
	}
	outcome.Published = true
	outcome.Page = pageRaw
	return json.Marshal(outcome)
}
