package exec

import (
	"context"
	"fmt"
	"net/url"
	"strings"

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

// reviewRemote adapts the runtime client to review-context operations. It
// resolves the thread target to an internal scope id via POST /resolve-channel,
// then hits the review-context endpoints.
type reviewRemote struct {
	client  *apiclient.Client
	read    remoteReadTransport
	natural naturalMutationTransport
}

type reviewScope struct {
	threadTarget string
	taskScopeID  string
}

func (remote reviewRemote) readTransport() remoteReadTransport {
	transport := remote.read
	transport.client = remote.client
	return transport
}

func (remote reviewRemote) naturalTransport() naturalMutationTransport {
	transport := remote.natural
	transport.client = remote.client
	return transport
}

type reviewContext struct {
	ID             string `json:"id"`
	Kind           string `json:"kind,omitempty"`
	RootPath       string `json:"rootPath,omitempty"`
	Name           string `json:"name,omitempty"`
	BaseRef        string `json:"baseRef,omitempty"`
	HeadRef        string `json:"headRef,omitempty"`
	PRURL          string `json:"prUrl,omitempty"`
	ComparisonMode string `json:"comparisonMode,omitempty"`
	ThreadID       string `json:"threadId,omitempty"`
	SourceAgentID  string `json:"sourceAgentId,omitempty"`
	TaskScopeID    string `json:"taskScopeId,omitempty"`
}

type reviewContextTransport struct {
	ID             string `json:"id"`
	Kind           string `json:"kind,omitempty"`
	RootPath       string `json:"rootPath,omitempty"`
	DisplayName    string `json:"displayName,omitempty"`
	BaseRef        string `json:"baseRef,omitempty"`
	HeadRef        string `json:"headRef,omitempty"`
	PRURL          string `json:"prUrl,omitempty"`
	ComparisonMode string `json:"comparisonMode,omitempty"`
	ThreadID       string `json:"threadId,omitempty"`
	SourceAgentID  string `json:"sourceAgentId,omitempty"`
	TaskScopeID    string `json:"taskScopeId,omitempty"`
}

type reviewListTransportResponse struct {
	Contexts   []reviewContextTransport `json:"contexts"`
	HasMore    bool                     `json:"hasMore"`
	NextCursor string                   `json:"nextCursor"`
}

type reviewListTransportResult struct {
	Contexts   []reviewContext
	HasMore    bool
	NextCursor string
}

type reviewAddRequest struct {
	Path  string `json:"path"`
	Base  string `json:"base,omitempty"`
	Head  string `json:"head,omitempty"`
	Name  string `json:"name,omitempty"`
	PRURL string `json:"prUrl,omitempty"`
}

// checkClient gates every adapter call so a nil Client or missing credentials
// returns a stable error instead of panicking.
func (a reviewRemote) checkClient() error {
	return requireRuntimeClient(a.client)
}

func resolveReviewContextScope(ctx context.Context, client *apiclient.Client, target string) (apiclient.ResolvedTarget, error) {
	resolved, err := client.ResolveTarget(ctx, target, apiclient.ResolvePurposeReviewContext)
	if err != nil {
		return apiclient.ResolvedTarget{}, err
	}
	return resolved, nil
}

func (a reviewRemote) listPage(ctx context.Context, threadTarget string, input reviewListRequest) (reviewListTransportResult, error) {
	if err := a.checkClient(); err != nil {
		return reviewListTransportResult{}, err
	}
	resolved, err := resolveReviewContextScope(ctx, a.client, threadTarget)
	if err != nil {
		return reviewListTransportResult{}, err
	}
	id := resolved.ScopeID
	values := url.Values{}
	if input.Limit > 0 {
		values.Set("limit", fmt.Sprintf("%d", input.Limit))
	}
	if cursor := strings.TrimSpace(input.Cursor); cursor != "" {
		values.Set("cursor", cursor)
	}
	path := a.client.AgentRuntimePath("/threads/" + url.PathEscape(id) + "/review-contexts")
	if encoded := values.Encode(); encoded != "" {
		path += "?" + encoded
	}
	var resp reviewListTransportResponse
	if err := a.readTransport().get(ctx, path, &resp); err != nil {
		resolved.InvalidateOnError(err)
		return reviewListTransportResult{}, err
	}
	return reviewListTransportResult{
		Contexts: reviewContextsFromTransport(resp.Contexts), HasMore: resp.HasMore, NextCursor: strings.TrimSpace(resp.NextCursor),
	}, nil
}

// list preserves the small-call helper used by older Desktop callers while
// the Exec action uses listPage to retain continuation metadata.
func normalizeReviewRemoteScope(input any) (reviewScope, error) {
	switch value := input.(type) {
	case reviewScope:
		return value, nil
	case string:
		return reviewScopeFromInput(value, nil)
	default:
		return reviewScope{}, NewError(CodeValidation, "review scope is required")
	}
}

func (a reviewRemote) list(ctx context.Context, input any) ([]reviewContext, error) {
	scope, err := normalizeReviewRemoteScope(input)
	if err != nil {
		return nil, err
	}
	if err := a.checkClient(); err != nil {
		return nil, err
	}
	if scope.taskScopeID == "" {
		page, err := a.listPage(ctx, scope.threadTarget, reviewListRequest{})
		if err != nil {
			return nil, err
		}
		return page.Contexts, nil
	}
	var resp struct {
		Contexts []reviewContextTransport `json:"contexts"`
	}
	if err := a.client.Get(ctx, a.client.AgentRuntimePath("/tasks/"+url.PathEscape(scope.taskScopeID)+"/review-contexts"), &resp); err != nil {
		return nil, err
	}
	return reviewContextsFromTransport(resp.Contexts), nil
}

func (a reviewRemote) add(ctx context.Context, input any, req reviewAddRequest) (reviewContext, error) {
	scope, err := normalizeReviewRemoteScope(input)
	if err != nil {
		return reviewContext{}, err
	}
	if err := a.checkClient(); err != nil {
		return reviewContext{}, err
	}
	body := map[string]any{
		"kind":        "git_repo",
		"rootPath":    req.Path,
		"displayName": req.Name,
		"headRef":     req.Head,
		"baseRef":     req.Base,
		"prUrl":       req.PRURL,
	}
	var resp struct {
		Context reviewContextTransport `json:"context"`
	}
	if scope.taskScopeID != "" {
		body["taskScope"] = map[string]string{"kind": "task", "scopeId": scope.taskScopeID}
		if err := a.client.Put(ctx, a.client.AgentRuntimePath("/tasks/"+url.PathEscape(scope.taskScopeID)+"/review-context"), body, &resp); err != nil {
			return reviewContext{}, err
		}
		return reviewContextFromTransport(resp.Context), nil
	}
	resolved, err := resolveReviewContextScope(ctx, a.client, scope.threadTarget)
	if err != nil {
		return reviewContext{}, err
	}
	if err := a.naturalTransport().put(ctx, a.client.AgentRuntimePath("/threads/"+url.PathEscape(resolved.ScopeID)+"/review-context"), body, &resp); err != nil {
		resolved.InvalidateOnError(err)
		return reviewContext{}, err
	}
	return reviewContextFromTransport(resp.Context), nil
}

func reviewContextsFromTransport(items []reviewContextTransport) []reviewContext {
	if items == nil {
		return nil
	}
	out := make([]reviewContext, 0, len(items))
	for _, item := range items {
		out = append(out, reviewContextFromTransport(item))
	}
	return out
}

func reviewContextFromTransport(item reviewContextTransport) reviewContext {
	return reviewContext{
		ID: item.ID, Kind: item.Kind, RootPath: item.RootPath, Name: item.DisplayName,
		BaseRef: item.BaseRef, HeadRef: item.HeadRef, PRURL: item.PRURL,
		ComparisonMode: item.ComparisonMode, ThreadID: item.ThreadID,
		SourceAgentID: item.SourceAgentID, TaskScopeID: item.TaskScopeID,
	}
}

func (a reviewRemote) delete(ctx context.Context, contextID string) error {
	if err := a.checkClient(); err != nil {
		return err
	}
	return a.naturalTransport().delete(ctx, a.client.AgentRuntimePath("/review-contexts/"+url.PathEscape(contextID)), nil)
}

func reviewListSummary(contexts []reviewContext) string {
	if len(contexts) == 0 {
		return "No review contexts configured."
	}
	return fmt.Sprintf("Listed %d review context%s.", len(contexts), pluralS(len(contexts)))
}
