package exec

import (
	"context"
	"fmt"
	"strings"

	invopopjsonschema "github.com/invopop/jsonschema"
)

type scheduledMessageRecord struct {
	ID                    string                     `json:"id,omitempty"`
	Name                  string                     `json:"name"`
	Status                string                     `json:"status"`
	Schedule              scheduledMessageSchedule   `json:"schedule"`
	Target                scheduledMessageTarget     `json:"target"`
	Template              string                     `json:"template"`
	Mentions              []scheduledMessageMention  `json:"mentions,omitempty"`
	StartThread           bool                       `json:"startThread,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"`
	Validation            scheduledMessageValidation `json:"validation"`
	CreatedAt             string                     `json:"createdAt"`
	UpdatedAt             string                     `json:"updatedAt"`
}

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

type scheduledMessageMention struct {
	Type  string `json:"type"`
	ID    string `json:"id"`
	Token string `json:"token"`
}

var scheduledMessageListSpec = actionSpec[scheduledMessageListRequest, scheduledMessageListResult]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "list",
		Summary:    "list scheduled messages",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[scheduledMessageListRequest]{
		{Title: "list scheduled messages", Input: scheduledMessageListRequest{}},
	},
}

var scheduledMessageGetSpec = actionSpec[scheduledMessageIDRequest, scheduledMessageRecord]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "get",
		Summary:    "get one scheduled message",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[scheduledMessageIDRequest]{
		{Title: "get scheduled message", Input: scheduledMessageIDRequest{ID: "sch_1"}},
	},
}

type scheduledMessageRun struct {
	ID                     string                 `json:"id"`
	ScheduleID             string                 `json:"scheduleId,omitempty"`
	TriggerID              string                 `json:"triggerId,omitempty"`
	WorkspaceID            string                 `json:"workspaceId"`
	IdempotencyKey         string                 `json:"idempotencyKey"`
	SourceOccurrenceRef    string                 `json:"sourceOccurrenceRef"`
	SourceKind             string                 `json:"sourceKind"`
	TimeMode               string                 `json:"timeMode,omitempty"`
	Status                 string                 `json:"status"`
	ScheduledFor           string                 `json:"scheduledFor,omitempty"`
	OccurredAt             string                 `json:"occurredAt,omitempty"`
	AttemptedAt            string                 `json:"attemptedAt"`
	CompletedAt            string                 `json:"completedAt,omitempty"`
	MessageID              string                 `json:"messageId,omitempty"`
	EffectKind             string                 `json:"effectKind,omitempty"`
	EffectRef              string                 `json:"effectRef,omitempty"`
	ErrorCode              string                 `json:"errorCode,omitempty"`
	ErrorDetail            string                 `json:"errorDetail,omitempty"`
	RenderedContentPreview string                 `json:"renderedContentPreview"`
	AttemptCount           int                    `json:"attemptCount"`
	Target                 scheduledMessageTarget `json:"target"`
	ScheduleName           string                 `json:"scheduleName,omitempty"`
	TriggerName            string                 `json:"triggerName,omitempty"`
}

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

var scheduledMessageRunsSpec = actionSpec[scheduledMessageRunsRequest, scheduledMessageRunsResult]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "runs",
		Summary:    "inspect recent runs",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[scheduledMessageRunsRequest]{
		{Title: "recent runs page", Input: scheduledMessageRunsRequest{ID: "sch_1", Limit: 20, Cursor: "<nextCursor>"}},
	},
}

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

var scheduledMessageValidateSpec = actionSpec[scheduledMessageValidateRequest, scheduledMessageValidationResult]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "validate",
		Summary:    "validate/preview without persisting",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[scheduledMessageValidateRequest]{
		{Title: "validate reminder", Input: scheduledMessageValidateRequest{scheduledMessageWriteRequest: scheduledMessageExampleWrite()}},
	},
}

var scheduledMessageCreateSpec = actionSpec[scheduledMessageCreateRequest, scheduledMessageRecord]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "create",
		Summary:    "create an active scheduled message",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[scheduledMessageCreateRequest]{
		{Title: "create reminder", Input: scheduledMessageCreateRequest{scheduledMessageWriteRequest: scheduledMessageExampleWrite(), IdempotencyKey: "sch-create-01"}},
	},
}

var scheduledMessageUpdateSpec = actionSpec[scheduledMessageUpdateRequest, scheduledMessageRecord]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "update",
		Summary:    "update a scheduled message",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[scheduledMessageUpdateRequest]{
		{
			Title: "update reminder",
			Input: scheduledMessageUpdateRequest{
				ID: "sch_1", scheduledMessageWriteRequest: scheduledMessageExampleWrite(), IdempotencyKey: "sch-update-01",
			},
		},
	},
}

var scheduledMessagePauseSpec = actionSpec[scheduledMessageIDRequest, scheduledMessageRecord]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "pause",
		Summary:    "pause an active scheduled message",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[scheduledMessageIDRequest]{
		{Title: "pause scheduled message", Input: scheduledMessageIDRequest{ID: "sch_1"}},
	},
}

var scheduledMessageResumeSpec = actionSpec[scheduledMessageIDRequest, scheduledMessageRecord]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "resume",
		Summary:    "resume a paused scheduled message",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[scheduledMessageIDRequest]{
		{Title: "resume scheduled message", Input: scheduledMessageIDRequest{ID: "sch_1"}},
	},
}

type scheduledMessageDeleteResult struct {
	ID string `json:"id"`
}

var scheduledMessageDeleteSpec = actionSpec[scheduledMessageIDRequest, scheduledMessageDeleteResult]{
	Meta: actionMeta{
		Domain:     "scheduled-message",
		Name:       "delete",
		Summary:    "permanently delete a scheduled message",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[scheduledMessageIDRequest]{
		{Title: "delete scheduled message", Input: scheduledMessageIDRequest{ID: "sch_1"}},
	},
}

func scheduledMessageExampleWrite() scheduledMessageWriteRequest {
	return scheduledMessageWriteRequest{
		Name:     "reminder",
		Schedule: scheduledMessageSchedule{Kind: "after", Seconds: 600},
		Target:   "#agentteam",
		Template: "Check build status",
	}
}

type scheduledMessageHandler struct {
	read        remoteReadTransport
	natural     naturalMutationTransport
	idempotency idempotencyReceiptTransport
}

func (handler scheduledMessageHandler) remote(authorized runtimeAuthorization) scheduledMessageRemote {
	return scheduledMessageRemote{
		client: authorized.Server, read: handler.read, natural: handler.natural,
		idempotency: handler.idempotency,
	}
}

func (handler scheduledMessageHandler) create(ctx context.Context, request actionScopedJSON[scheduledMessageCreateRequest]) (actionResult[scheduledMessageRecord], error) {
	if err := validateWrite(request.Body.scheduledMessageWriteRequest); err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	result, err := handler.remote(request.Authorized).create(ctx, request.Body)
	return scheduledMessageMutationActionResult(result, err, "create scheduled message failed", "Created scheduled message.")
}

func (handler scheduledMessageHandler) delete(ctx context.Context, request actionScopedJSON[scheduledMessageIDRequest]) (actionResult[scheduledMessageDeleteResult], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageDeleteResult]{}, err
	}
	if err := handler.remote(request.Authorized).delete(ctx, id); err != nil {
		return actionResult[scheduledMessageDeleteResult]{}, classifyScheduledMessageError(err, "delete scheduled message failed")
	}
	return actionResult[scheduledMessageDeleteResult]{
		Data: scheduledMessageDeleteResult{ID: id}, Text: "Deleted scheduled message: " + id,
	}, nil
}

func (handler scheduledMessageHandler) get(ctx context.Context, request actionScopedJSON[scheduledMessageIDRequest]) (actionResult[scheduledMessageRecord], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	result, err := handler.remote(request.Authorized).get(ctx, id)
	return scheduledMessageMutationActionResult(result, err, "get scheduled message failed", "Got scheduled message: "+id)
}

func (handler scheduledMessageHandler) list(ctx context.Context, request actionScopedJSON[scheduledMessageListRequest]) (actionResult[scheduledMessageListResult], error) {
	limit := request.Body.Limit
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}
	input := request.Body
	input.Limit = limit
	input.Cursor = strings.TrimSpace(input.Cursor)
	result, err := handler.remote(request.Authorized).list(ctx, input)
	if err != nil {
		return actionResult[scheduledMessageListResult]{}, classifyScheduledMessageError(err, "list scheduled messages failed")
	}
	if result.Items == nil {
		result.Items = []scheduledMessageListItem{}
	}
	text := fmt.Sprintf("Listed %d scheduled message%s.", len(result.Items), pluralS(len(result.Items)))
	if len(result.Items) == 0 {
		text = "No scheduled messages."
	}
	return actionResult[scheduledMessageListResult]{Data: result, Text: text}, nil
}

func (handler scheduledMessageHandler) pause(ctx context.Context, request actionScopedJSON[scheduledMessageIDRequest]) (actionResult[scheduledMessageRecord], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	result, err := handler.remote(request.Authorized).pause(ctx, id)
	return scheduledMessageMutationActionResult(result, err, "pause scheduled message failed", "Paused scheduled message: "+id)
}

func (handler scheduledMessageHandler) resume(ctx context.Context, request actionScopedJSON[scheduledMessageIDRequest]) (actionResult[scheduledMessageRecord], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	result, err := handler.remote(request.Authorized).resume(ctx, id)
	return scheduledMessageMutationActionResult(result, err, "resume scheduled message failed", "Resumed scheduled message: "+id)
}

func (handler scheduledMessageHandler) runs(ctx context.Context, request actionScopedJSON[scheduledMessageRunsRequest]) (actionResult[scheduledMessageRunsResult], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageRunsResult]{}, err
	}
	limit := request.Body.Limit
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}
	input := request.Body
	input.ID = id
	input.Limit = limit
	input.Cursor = strings.TrimSpace(input.Cursor)
	result, err := handler.remote(request.Authorized).runsPage(ctx, input)
	if err != nil {
		return actionResult[scheduledMessageRunsResult]{}, classifyScheduledMessageError(err, "list scheduled-message runs failed")
	}
	if result.Runs == nil {
		result.Runs = []scheduledMessageRun{}
	}
	return actionResult[scheduledMessageRunsResult]{
		Data: result,
		Text: fmt.Sprintf("Listed %d run%s.", len(result.Runs), pluralS(len(result.Runs))),
	}, nil
}

func (handler scheduledMessageHandler) update(ctx context.Context, request actionScopedJSON[scheduledMessageUpdateRequest]) (actionResult[scheduledMessageRecord], error) {
	id, err := scheduledMessageID(request.Body.ID)
	if err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	if err := validateWrite(request.Body.scheduledMessageWriteRequest); err != nil {
		return actionResult[scheduledMessageRecord]{}, err
	}
	result, err := handler.remote(request.Authorized).update(ctx, id, request.Body)
	return scheduledMessageMutationActionResult(result, err, "update scheduled message failed", "Updated scheduled message: "+id)
}

func (handler scheduledMessageHandler) validate(ctx context.Context, request actionScopedJSON[scheduledMessageValidateRequest]) (actionResult[scheduledMessageValidationResult], error) {
	if err := validateWrite(request.Body.scheduledMessageWriteRequest); err != nil {
		return actionResult[scheduledMessageValidationResult]{}, err
	}
	result, err := handler.remote(request.Authorized).validate(ctx, request.Body)
	if err != nil {
		return actionResult[scheduledMessageValidationResult]{}, classifyScheduledMessageError(err, "validate scheduled message failed")
	}
	return actionResult[scheduledMessageValidationResult]{Data: result, Text: "Validated scheduled message."}, nil
}

func validateWrite(input scheduledMessageWriteRequest) error {
	if strings.TrimSpace(input.Name) == "" || strings.TrimSpace(input.Target) == "" ||
		strings.TrimSpace(input.Template) == "" {
		return NewError(CodeValidation, "name, schedule, target, and template are required")
	}
	switch input.Schedule.Kind {
	case "at":
		if strings.TrimSpace(input.Schedule.At) == "" {
			return NewError(CodeValidation, "schedule.at is required when schedule.kind is at")
		}
	case "after", "every":
		if input.Schedule.Seconds <= 0 {
			return NewError(CodeValidation, "schedule.seconds must be positive when schedule.kind is "+input.Schedule.Kind)
		}
		if input.Schedule.Kind == "every" && strings.TrimSpace(input.Schedule.Anchor) == "" {
			return NewError(CodeValidation, "schedule.anchor is required when schedule.kind is every")
		}
	case "cron":
		if strings.TrimSpace(input.Schedule.Expression) == "" || strings.TrimSpace(input.Schedule.Timezone) == "" {
			return NewError(CodeValidation, "schedule.expression and schedule.timezone are required when schedule.kind is cron")
		}
	default:
		return NewError(CodeValidation, "schedule.kind must be at, after, every, or cron")
	}
	return nil
}

func scheduledMessageID(value string) (string, error) {
	id := strings.TrimSpace(value)
	if id == "" {
		return "", NewError(CodeValidation, "id is required")
	}
	return id, nil
}

func scheduledMessageMutationActionResult(
	result scheduledMessageRecord,
	err error,
	problem string,
	text string,
) (actionResult[scheduledMessageRecord], error) {
	if err != nil {
		return actionResult[scheduledMessageRecord]{}, classifyScheduledMessageError(err, problem)
	}
	return actionResult[scheduledMessageRecord]{Data: result, Text: text}, nil
}

func classifyScheduledMessageError(err error, problem string) *Error {
	if typed, ok := err.(*Error); ok {
		return typed
	}
	return classifyAPIError(err, problem)
}

type scheduledMessageListRequest struct {
	Limit  int    `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor string `json:"cursor,omitempty" jsonschema:"maxLength=256"`
	Status string `json:"status,omitempty"`
	Source string `json:"source,omitempty"`
	Target string `json:"target,omitempty"`
}
type scheduledMessageIDRequest struct {
	ID string `json:"id" jsonschema:"minLength=1,maxLength=256"`
}
type scheduledMessageRunsRequest struct {
	ID     string `json:"id" jsonschema:"minLength=1,maxLength=256"`
	Limit  int    `json:"limit,omitempty" jsonschema:"minimum=1,maximum=100"`
	Cursor string `json:"cursor,omitempty" jsonschema:"maxLength=512"`
}
type scheduledMessageSchedule struct {
	Kind       string `json:"kind" jsonschema:"enum=at,enum=after,enum=every,enum=cron"`
	At         string `json:"at,omitempty" jsonschema:"format=date-time"`
	Seconds    int64  `json:"seconds,omitempty" jsonschema:"minimum=1"`
	Anchor     string `json:"anchor,omitempty" jsonschema:"format=date-time"`
	Expression string `json:"expression,omitempty" jsonschema:"minLength=1,maxLength=256"`
	Timezone   string `json:"timezone,omitempty" jsonschema:"minLength=1"`
}

type scheduledMessageAtSchedule struct {
	Kind string `json:"kind" jsonschema:"enum=at"`
	At   string `json:"at" jsonschema:"format=date-time"`
}

type scheduledMessageAfterSchedule struct {
	Kind    string `json:"kind" jsonschema:"enum=after"`
	Seconds int64  `json:"seconds" jsonschema:"minimum=1"`
}

type scheduledMessageEverySchedule struct {
	Kind    string `json:"kind" jsonschema:"enum=every"`
	Seconds int64  `json:"seconds" jsonschema:"minimum=1"`
	Anchor  string `json:"anchor" jsonschema:"format=date-time"`
}

type scheduledMessageCronSchedule struct {
	Kind       string `json:"kind" jsonschema:"enum=cron"`
	Expression string `json:"expression" jsonschema:"minLength=1,maxLength=256"`
	Timezone   string `json:"timezone" jsonschema:"minLength=1"`
}

func (scheduledMessageSchedule) JSONSchema() *invopopjsonschema.Schema {
	reflector := invopopjsonschema.Reflector{Anonymous: true, DoNotReference: true, ExpandedStruct: true}
	schema := &invopopjsonschema.Schema{OneOf: []*invopopjsonschema.Schema{
		reflector.Reflect(scheduledMessageAtSchedule{}),
		reflector.Reflect(scheduledMessageAfterSchedule{}),
		reflector.Reflect(scheduledMessageEverySchedule{}),
		reflector.Reflect(scheduledMessageCronSchedule{}),
	}}
	for _, branch := range schema.OneOf {
		branch.Version = ""
	}
	return schema
}

type scheduledMessageWriteRequest struct {
	Name                  string                   `json:"name" jsonschema:"minLength=1"`
	Schedule              scheduledMessageSchedule `json:"schedule"`
	Target                string                   `json:"target,omitempty"`
	Template              string                   `json:"template" jsonschema:"minLength=1"`
	StartThread           bool                     `json:"startThread,omitempty"`
	HumanNotificationMode string                   `json:"humanNotificationMode,omitempty" jsonschema:"enum=none"`
}
type scheduledMessageValidateRequest struct {
	ID string `json:"id,omitempty" jsonschema:"minLength=1"`
	scheduledMessageWriteRequest
}
type scheduledMessageCreateRequest struct {
	scheduledMessageWriteRequest
	IdempotencyKey string `json:"idempotencyKey" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
}
type scheduledMessageUpdateRequest struct {
	ID string `json:"id" jsonschema:"minLength=1"`
	scheduledMessageWriteRequest
	IdempotencyKey    string `json:"idempotencyKey" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	ExpectedUpdatedAt string `json:"expectedUpdatedAt,omitempty" jsonschema:"format=date-time"`
}

func (scheduledMessageUpdateRequest) JSONSchema() *invopopjsonschema.Schema {
	sc := mustTypedWorkflowSchema(mustWorkflowSchema(closedWorkflowObject(
		map[string]any{
			"id":                    map[string]any{"type": "string", "minLength": 1},
			"name":                  map[string]any{"type": "string", "minLength": 1},
			"target":                map[string]any{"type": "string", "minLength": 1},
			"template":              map[string]any{"type": "string", "minLength": 1},
			"startThread":           map[string]any{"type": "boolean"},
			"humanNotificationMode": map[string]any{"type": "string", "enum": []any{"none"}},
			"idempotencyKey":        map[string]any{"type": "string", "minLength": 1, "maxLength": 128, "pattern": `.*\S.*`},
			"expectedUpdatedAt":     map[string]any{"type": "string", "format": "date-time"},
		},
		"id", "name", "target", "template", "idempotencyKey",
	)))
	sc.Properties.Set("schedule", (&scheduledMessageSchedule{}).JSONSchema())
	sc.Required = append(sc.Required, "schedule")
	return sc
}
