package exec

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"strings"

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

type scheduledMessageRemote struct {
	client      *apiclient.Client
	read        remoteReadTransport
	natural     naturalMutationTransport
	idempotency idempotencyReceiptTransport
}

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

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

func (remote scheduledMessageRemote) idempotencyTransport() idempotencyReceiptTransport {
	transport := remote.idempotency
	transport.client = remote.client
	return transport
}

type scheduledMessageListItem struct {
	ID               string                 `json:"id"`
	Name             string                 `json:"name"`
	Status           string                 `json:"status"`
	SourceKind       string                 `json:"sourceKind,omitempty"`
	TimeMode         string                 `json:"timeMode,omitempty"`
	Target           scheduledMessageTarget `json:"target,omitempty"`
	NextOccurrenceAt string                 `json:"nextOccurrenceAt,omitempty"`
	LastFailureCode  string                 `json:"lastFailureCode,omitempty"`
}

type scheduledMessageTarget struct {
	Kind            string `json:"kind,omitempty"`
	Ref             string `json:"ref,omitempty"`
	Label           string `json:"label,omitempty"`
	ParentChannelID string `json:"parentChannelId,omitempty"`
	ParentMessageID string `json:"parentMessageId,omitempty"`
}

type scheduledMessageListResult struct {
	Items      []scheduledMessageListItem `json:"items"`
	Limit      int                        `json:"limit,omitempty"`
	Cursor     string                     `json:"cursor,omitempty"`
	HasMore    bool                       `json:"hasMore,omitempty"`
	NextCursor string                     `json:"nextCursor,omitempty"`
}

type scheduledMessageListTransportResponse struct {
	Schedules  []scheduledMessageListItem `json:"schedules"`
	Limit      int                        `json:"limit"`
	Cursor     string                     `json:"cursor"`
	HasMore    bool                       `json:"hasMore"`
	NextCursor string                     `json:"nextCursor"`
}

func (response *scheduledMessageListTransportResponse) UnmarshalJSON(data []byte) error {
	data = bytes.TrimSpace(data)
	if len(data) > 0 && data[0] == '[' {
		return json.Unmarshal(data, &response.Schedules)
	}
	type page scheduledMessageListTransportResponse
	return json.Unmarshal(data, (*page)(response))
}

type scheduledMessageRecordTransport struct {
	ID                    string                              `json:"id,omitempty"`
	Name                  string                              `json:"name"`
	Status                string                              `json:"status"`
	SourceKind            string                              `json:"sourceKind"`
	TimeMode              string                              `json:"timeMode,omitempty"`
	EventSubscription     *scheduledMessageEventSubscription  `json:"eventSubscription,omitempty"`
	ScheduledAt           string                              `json:"scheduledAt,omitempty"`
	RelativeSeconds       *int64                              `json:"relativeSeconds,omitempty"`
	AnchorAt              string                              `json:"anchorAt,omitempty"`
	IntervalSeconds       *int64                              `json:"intervalSeconds,omitempty"`
	CronExpression        string                              `json:"cronExpression,omitempty"`
	Timezone              string                              `json:"timezone,omitempty"`
	Target                scheduledMessageTarget              `json:"target"`
	MessageTemplate       string                              `json:"messageTemplate"`
	Mentions              []scheduledMessageMention           `json:"mentions,omitempty"`
	AlsoStartThread       bool                                `json:"alsoStartThread,omitempty"`
	HumanNotificationMode string                              `json:"humanNotificationMode"`
	NextOccurrenceAt      string                              `json:"nextOccurrenceAt,omitempty"`
	LastRunAt             string                              `json:"lastRunAt,omitempty"`
	LastSuccessAt         string                              `json:"lastSuccessAt,omitempty"`
	LastFailureCode       string                              `json:"lastFailureCode,omitempty"`
	CreatedAt             string                              `json:"createdAt"`
	UpdatedAt             string                              `json:"updatedAt"`
	Validation            scheduledMessageValidationTransport `json:"validation"`
}

type scheduledMessageEventSubscription struct {
	EventKinds []string                                `json:"eventKinds,omitempty"`
	Filter     scheduledMessageEventSubscriptionFilter `json:"filter,omitempty"`
}

type scheduledMessageEventSubscriptionFilter struct {
	ResourceIdentifier string `json:"resourceIdentifier,omitempty"`
	ConnectionRef      string `json:"connectionRef,omitempty"`
}

type scheduledMessageValidationTransport struct {
	Valid   bool   `json:"valid"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (record scheduledMessageRecordTransport) model() scheduledMessageRecord {
	return scheduledMessageRecord{
		ID:                    record.ID,
		Name:                  record.Name,
		Status:                record.Status,
		Schedule:              scheduleFromTransport(record),
		Target:                record.Target,
		Template:              record.MessageTemplate,
		Mentions:              record.Mentions,
		StartThread:           record.AlsoStartThread,
		HumanNotificationMode: record.HumanNotificationMode,
		NextOccurrenceAt:      record.NextOccurrenceAt,
		LastRunAt:             record.LastRunAt,
		LastSuccessAt:         record.LastSuccessAt,
		LastFailureCode:       record.LastFailureCode,
		Validation: scheduledMessageValidation{
			Valid: record.Validation.Valid, Code: record.Validation.Code, Message: record.Validation.Message,
		},
		CreatedAt: record.CreatedAt,
		UpdatedAt: record.UpdatedAt,
	}
}

func scheduleFromTransport(record scheduledMessageRecordTransport) scheduledMessageSchedule {
	switch record.TimeMode {
	case "one_shot_absolute":
		return scheduledMessageSchedule{Kind: "at", At: record.ScheduledAt}
	case "one_shot_relative":
		return scheduledMessageSchedule{Kind: "after", Seconds: int64Value(record.RelativeSeconds)}
	case "interval":
		return scheduledMessageSchedule{
			Kind: "every", Seconds: int64Value(record.IntervalSeconds), Anchor: record.AnchorAt,
		}
	case "cron":
		return scheduledMessageSchedule{
			Kind: "cron", Expression: record.CronExpression, Timezone: record.Timezone,
		}
	default:
		return scheduledMessageSchedule{Kind: record.TimeMode}
	}
}

func int64Value(value *int64) int64 {
	if value == nil {
		return 0
	}
	return *value
}

type scheduledMessageMutationTransportRequest struct {
	Name                  string                   `json:"name"`
	Schedule              scheduledMessageSchedule `json:"schedule"`
	Target                string                   `json:"target"`
	Template              string                   `json:"template"`
	StartThread           bool                     `json:"startThread,omitempty"`
	HumanNotificationMode string                   `json:"humanNotificationMode,omitempty"`
	IdempotencyKey        string                   `json:"idempotencyKey,omitempty"`
	ExpectedUpdatedAt     string                   `json:"expectedUpdatedAt,omitempty"`
}

func scheduledMessageMutationTransport(input scheduledMessageWriteRequest) scheduledMessageMutationTransportRequest {
	return scheduledMessageMutationTransportRequest{
		Name:                  input.Name,
		Schedule:              input.Schedule,
		Target:                input.Target,
		Template:              input.Template,
		StartThread:           input.StartThread,
		HumanNotificationMode: input.HumanNotificationMode,
	}
}

type scheduledMessageValidateTransportRequest struct {
	scheduledMessageMutationTransportRequest
	ScheduleID string `json:"scheduleId,omitempty"`
}

type scheduledMessageValidationTransportResult struct {
	Current              *scheduledMessageRecordTransport `json:"current,omitempty"`
	Proposed             scheduledMessageRecordTransport  `json:"proposed"`
	EstimatedScheduledAt string                           `json:"estimatedScheduledAt,omitempty"`
}

func (result scheduledMessageValidationTransportResult) model() scheduledMessageValidationResult {
	var current *scheduledMessageRecord
	if result.Current != nil {
		mapped := result.Current.model()
		current = &mapped
	}
	return scheduledMessageValidationResult{
		Current: current, Proposed: result.Proposed.model(), EstimatedScheduledAt: result.EstimatedScheduledAt,
	}
}

type scheduledMessageRunsTransportResponse struct {
	Runs       []scheduledMessageRun `json:"runs"`
	HasMore    bool                  `json:"hasMore"`
	NextCursor string                `json:"nextCursor"`
}

func (response *scheduledMessageRunsTransportResponse) UnmarshalJSON(data []byte) error {
	data = bytes.TrimSpace(data)
	if len(data) > 0 && data[0] == '[' {
		return json.Unmarshal(data, &response.Runs)
	}
	type envelope scheduledMessageRunsTransportResponse
	if err := json.Unmarshal(data, (*envelope)(response)); err != nil {
		return fmt.Errorf("invalid runs response: %w", err)
	}
	return nil
}

func (a scheduledMessageRemote) list(ctx context.Context, input scheduledMessageListRequest) (scheduledMessageListResult, error) {
	if err := requireRuntimeClient(a.client); err != nil {
		return scheduledMessageListResult{}, err
	}
	query := url.Values{}
	query.Set("limit", fmt.Sprintf("%d", input.Limit))
	query.Set("page", "1")
	if input.Cursor != "" {
		query.Set("cursor", input.Cursor)
	}
	if input.Status != "" {
		query.Set("status", strings.TrimSpace(input.Status))
	}
	if input.Source != "" {
		query.Set("source", strings.TrimSpace(input.Source))
	}
	if input.Target != "" {
		query.Set("target", strings.TrimSpace(input.Target))
	}
	path := a.client.AgentRuntimePath("/schedules?" + query.Encode())
	var response scheduledMessageListTransportResponse
	if err := a.readTransport().get(ctx, path, &response); err != nil {
		return scheduledMessageListResult{}, err
	}
	items := response.Schedules
	if items == nil {
		items = []scheduledMessageListItem{}
	}
	return scheduledMessageListResult{
		Items: items, Limit: response.Limit, Cursor: response.Cursor,
		HasMore: response.HasMore, NextCursor: strings.TrimSpace(response.NextCursor),
	}, nil
}

func (a scheduledMessageRemote) get(ctx context.Context, id string) (scheduledMessageRecord, error) {
	if err := requireRuntimeClient(a.client); err != nil {
		return scheduledMessageRecord{}, err
	}
	var result scheduledMessageRecordTransport
	if err := a.readTransport().get(ctx, a.client.AgentRuntimePath("/schedules/"+url.PathEscape(id)), &result); err != nil {
		return scheduledMessageRecord{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) runsPage(ctx context.Context, input scheduledMessageRunsRequest) (scheduledMessageRunsResult, error) {
	if err := requireRuntimeClient(a.client); err != nil {
		return scheduledMessageRunsResult{}, err
	}
	values := url.Values{}
	values.Set("limit", fmt.Sprintf("%d", input.Limit))
	if cursor := strings.TrimSpace(input.Cursor); cursor != "" {
		values.Set("cursor", cursor)
	}
	path := a.client.AgentRuntimePath("/schedules/" + url.PathEscape(input.ID) + "/runs?" + values.Encode())
	var response scheduledMessageRunsTransportResponse
	if err := a.readTransport().get(ctx, path, &response); err != nil {
		return scheduledMessageRunsResult{}, err
	}
	if response.Runs == nil {
		response.Runs = []scheduledMessageRun{}
	}
	return scheduledMessageRunsResult{Runs: response.Runs, HasMore: response.HasMore, NextCursor: strings.TrimSpace(response.NextCursor)}, nil
}

// runs preserves the small-call helper used by older Desktop callers while
// the Exec action uses runsPage to retain continuation metadata.
func (a scheduledMessageRemote) runs(ctx context.Context, id string, limit int) ([]scheduledMessageRun, error) {
	if limit <= 0 {
		limit = 20
	}
	page, err := a.runsPage(ctx, scheduledMessageRunsRequest{ID: id, Limit: limit})
	if err != nil {
		return nil, err
	}
	return page.Runs, nil
}

func (a scheduledMessageRemote) validate(ctx context.Context, input scheduledMessageValidateRequest) (scheduledMessageValidationResult, error) {
	body := scheduledMessageValidateTransportRequest{
		scheduledMessageMutationTransportRequest: scheduledMessageMutationTransport(input.scheduledMessageWriteRequest),
		ScheduleID:                               strings.TrimSpace(input.ID),
	}
	var result scheduledMessageValidationTransportResult
	if err := postScheduledMessageInto(ctx, a.readTransport(), a.client, "/schedules/validate", body, &result); err != nil {
		return scheduledMessageValidationResult{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) create(ctx context.Context, input scheduledMessageCreateRequest) (scheduledMessageRecord, error) {
	if err := requireRuntimeClient(a.client); err != nil {
		return scheduledMessageRecord{}, err
	}
	body := scheduledMessageMutationTransport(input.scheduledMessageWriteRequest)
	body.IdempotencyKey = strings.TrimSpace(input.IdempotencyKey)
	var result scheduledMessageRecordTransport
	var err error
	if strings.TrimSpace(body.IdempotencyKey) != "" {
		err = a.idempotencyTransport().post(ctx, a.client.AgentRuntimePath("/schedules"), body, &result, replayHeaders(body.IdempotencyKey))
	} else {
		err = a.naturalTransport().post(ctx, a.client.AgentRuntimePath("/schedules"), body, &result)
	}
	if err != nil {
		return scheduledMessageRecord{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) update(ctx context.Context, id string, input scheduledMessageUpdateRequest) (scheduledMessageRecord, error) {
	if err := requireRuntimeClient(a.client); err != nil {
		return scheduledMessageRecord{}, err
	}
	body := scheduledMessageMutationTransport(input.scheduledMessageWriteRequest)
	body.IdempotencyKey = strings.TrimSpace(input.IdempotencyKey)
	body.ExpectedUpdatedAt = strings.TrimSpace(input.ExpectedUpdatedAt)
	var result scheduledMessageRecordTransport
	var err error
	if strings.TrimSpace(body.IdempotencyKey) != "" {
		err = a.idempotencyTransport().patch(ctx, a.client.AgentRuntimePath("/schedules/"+url.PathEscape(id)), body, &result, replayHeaders(body.IdempotencyKey))
	} else {
		err = a.naturalTransport().patch(ctx, a.client.AgentRuntimePath("/schedules/"+url.PathEscape(id)), body, &result)
	}
	if err != nil {
		return scheduledMessageRecord{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) pause(ctx context.Context, id string) (scheduledMessageRecord, error) {
	var result scheduledMessageRecordTransport
	if err := postScheduledMessageMutationInto(ctx, a.naturalTransport(), a.client, "/schedules/"+url.PathEscape(id)+"/pause", struct{}{}, &result); err != nil {
		return scheduledMessageRecord{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) resume(ctx context.Context, id string) (scheduledMessageRecord, error) {
	var result scheduledMessageRecordTransport
	if err := postScheduledMessageMutationInto(ctx, a.naturalTransport(), a.client, "/schedules/"+url.PathEscape(id)+"/resume", struct{}{}, &result); err != nil {
		return scheduledMessageRecord{}, err
	}
	return result.model(), nil
}

func (a scheduledMessageRemote) delete(ctx context.Context, id string) error {
	if err := requireRuntimeClient(a.client); err != nil {
		return err
	}
	return a.naturalTransport().delete(ctx, a.client.AgentRuntimePath("/schedules/"+url.PathEscape(id)), nil)
}

func postScheduledMessageInto[I, O any](
	ctx context.Context,
	transport remoteReadTransport,
	client *apiclient.Client,
	suffix string,
	input I,
	output *O,
) error {
	if err := requireRuntimeClient(client); err != nil {
		return err
	}
	return transport.post(ctx, client.AgentRuntimePath(suffix), input, output)
}

func postScheduledMessageMutationInto[I, O any](
	ctx context.Context,
	transport naturalMutationTransport,
	client *apiclient.Client,
	suffix string,
	input I,
	output *O,
) error {
	if err := requireRuntimeClient(client); err != nil {
		return err
	}
	return transport.post(ctx, client.AgentRuntimePath(suffix), input, output)
}
