package agent

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	workflowpayload "github.com/tidbcloud/AgentTeam/internal/workflow/payload"
)

// workflowPayloadRef is returned on compact/default responses when a semantic
// value would exceed the inline Exec budget. The owning response identifies
// the resource, while the cursor itself is bound to those IDs and section.
type workflowPayloadRef struct {
	Cursor string `json:"cursor"`
}

type workflowPayloadPage struct {
	Section    string `json:"section"`
	Path       string `json:"path,omitempty"`
	Encoding   string `json:"encoding"`
	Value      string `json:"value"`
	Cursor     string `json:"cursor,omitempty"`
	NextCursor string `json:"nextCursor,omitempty"`
	Bytes      int    `json:"bytes"`
	TotalBytes int    `json:"totalBytes"`
	Complete   bool   `json:"complete"`
}

func workflowPayloadRefFrom(value *workflowpayload.Ref) *workflowPayloadRef {
	if value == nil {
		return nil
	}
	return &workflowPayloadRef{
		Cursor: value.Cursor,
	}
}

func workflowPayloadPageResponse(value workflowpayload.Page) workflowPayloadPage {
	return workflowPayloadPage{
		Section: value.Section, Path: value.Path, Encoding: value.Encoding,
		Value: value.Value, Cursor: value.Cursor, NextCursor: value.NextCursor,
		Bytes: value.Bytes, TotalBytes: value.TotalBytes, Complete: value.Complete,
	}
}

func inlineWorkflowJSONWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path string, raw json.RawMessage, snapshot workflowpayload.Snapshot) (json.RawMessage, *workflowPayloadRef, error) {
	value, ref, err := workflowpayload.InlineWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "json", raw, snapshot)
	return json.RawMessage(value), workflowPayloadRefFrom(ref), err
}

func workflowPayloadRefForJSONWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path string, size int, snapshot workflowpayload.Snapshot) (*workflowPayloadRef, error) {
	ref, err := workflowpayload.ReferenceWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "json", size, snapshot)
	return workflowPayloadRefFrom(ref), err
}

func workflowPayloadRefForTextWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path string, size int, snapshot workflowpayload.Snapshot) (*workflowPayloadRef, error) {
	ref, err := workflowpayload.ReferenceWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "text", size, snapshot)
	return workflowPayloadRefFrom(ref), err
}

func inlineWorkflowTextWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path, value string, snapshot workflowpayload.Snapshot) (string, *workflowPayloadRef, error) {
	inline, ref, err := workflowpayload.InlineWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "text", []byte(value), snapshot)
	return string(inline), workflowPayloadRefFrom(ref), err
}

func workflowPayloadPageForJSONWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path, cursor string, limit int, raw json.RawMessage, snapshot workflowpayload.Snapshot) (workflowPayloadPage, error) {
	section, path, cursor, limit, err := workflowPayloadReadRequest(section, path, cursor, limit)
	if err != nil {
		return workflowPayloadPage{}, err
	}
	narrowed, err := workflowpayload.NarrowJSON(raw, path)
	if err != nil {
		return workflowPayloadPage{}, err
	}
	if snapshot.Digest == "" {
		snapshot.Digest = workflowpayload.ContentDigest(raw)
	}
	snapshot.ValueDigest = workflowpayload.ContentDigest(narrowed)
	page, err := workflowpayload.PageBytesWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "json", narrowed, snapshot, cursor, limit)
	if err != nil {
		return workflowPayloadPage{}, err
	}
	return workflowPayloadPageResponse(page), nil
}

func workflowPayloadPageForTextWithSnapshot(kind, workspaceID, planID, versionID, runID, stepID, section, path, cursor string, limit int, value string, snapshot workflowpayload.Snapshot) (workflowPayloadPage, error) {
	section, path, cursor, limit, err := workflowPayloadReadRequest(section, path, cursor, limit)
	if err != nil {
		return workflowPayloadPage{}, err
	}
	page, err := workflowpayload.PageBytesWithSnapshot(workflowpayload.Identity{Kind: kind, WorkspaceID: workspaceID, PlanID: planID, VersionID: versionID, RunID: runID, StepID: stepID}, section, path, "text", []byte(value), snapshot, cursor, limit)
	if err != nil {
		return workflowPayloadPage{}, err
	}
	return workflowPayloadPageResponse(page), nil
}

func agentWorkflowPayloadLimit(limit *int) (int, error) {
	if limit == nil || *limit == 0 {
		return workflowpayload.MaxChunkBytes, nil
	}
	if *limit < 1 || *limit > workflowpayload.MaxChunkBytes {
		return 0, fmt.Errorf("limit must be between 1 and %d", workflowpayload.MaxChunkBytes)
	}
	return *limit, nil
}

func agentWorkflowPayloadError(action string, err error) error {
	if errors.Is(err, workflowpayload.ErrContentChanged) {
		return publicError(http.StatusConflict, action+"_payload_conflict", "payload snapshot changed; restart the read")
	}
	return publicError(http.StatusBadRequest, action+"_invalid_request", err.Error())
}

func workflowPayloadReadRequest(section, path, cursor string, limit int) (string, string, string, int, error) {
	section = strings.TrimSpace(section)
	path = strings.TrimSpace(path)
	cursor = strings.TrimSpace(cursor)
	if err := workflowpayload.ValidateReadSelector(section, path, cursor); err != nil {
		return "", "", "", 0, err
	}
	if limit == 0 {
		limit = workflowpayload.MaxChunkBytes
	}
	if limit < 1 || limit > workflowpayload.MaxChunkBytes {
		return "", "", "", 0, fmt.Errorf("limit must be between 1 and %d", workflowpayload.MaxChunkBytes)
	}
	return section, path, cursor, limit, nil
}
