package authoring

import (
	"encoding/json"
	"errors"
	"strings"

	"github.com/nomadcode/nomadcode-core/internal/model"
	"github.com/nomadcode/nomadcode-core/internal/storage"
	"github.com/nomadcode/nomadcode-core/internal/workitempipeline"
)

// pipelinePayload is the actual task.Payload shape produced by
// workitem.BuildCreateTaskInput. "message" carries the Plane ticket body
// (comment > description > title), and "work_item" carries the provider
// identity map (provider, tenant, project, id, state_id, external_url).
// "prompt" and "instructions" are accepted as a generic override shape.
type pipelinePayload struct {
	// pipeline shape (set by workitem.BuildCreateTaskInput)
	Message  string            `json:"message"`
	WorkItem map[string]string `json:"work_item"`
	// generic override shape
	Prompt       string `json:"prompt"`
	Instructions string `json:"instructions"`
}

const defaultAuthoringInstructions = "다음 메시지를 바탕으로 마일스톤을 작성해."

// PushOnlyCommand is the exact user-facing command sent to the workspace agent
// as the push-only authoring stage after the first authoring response succeeds.
const PushOnlyCommand = "변경된 내용에 대해 develop 브런치에 푸시해"

// ErrProviderIdentityMissing is returned when a Plane-origin authoring task has
// checkout metadata but no provider work item id from its external ref, payload,
// or external metadata.
var ErrProviderIdentityMissing = errors.New("authoring provider identity missing")

// isAuthoringTask returns true when the task is a Plane-origin authoring task
// that should be routed through the IOP workspace authoring bridge. The criteria
// are: source=="plane" and task metadata contains a "checkout" key with a
// non-empty slot path.
func isAuthoringTask(task storage.Task) (workitempipeline.CheckoutMetadata, bool) {
	if task.Source != "plane" {
		return workitempipeline.CheckoutMetadata{}, false
	}
	if len(task.Metadata) == 0 || string(task.Metadata) == "null" {
		return workitempipeline.CheckoutMetadata{}, false
	}
	var meta struct {
		Checkout *workitempipeline.CheckoutMetadata `json:"checkout"`
	}
	if err := json.Unmarshal(task.Metadata, &meta); err != nil || meta.Checkout == nil {
		return workitempipeline.CheckoutMetadata{}, false
	}
	if strings.TrimSpace(meta.Checkout.SlotPath) == "" {
		return workitempipeline.CheckoutMetadata{}, false
	}
	return *meta.Checkout, true
}

// TaskCheckoutMetadata returns the checkout metadata that makes a task a
// Plane-origin authoring task. It lets terminal finalizers reuse the same
// authoring-task boundary as the model input builder.
func TaskCheckoutMetadata(task storage.Task) (workitempipeline.CheckoutMetadata, bool) {
	return isAuthoringTask(task)
}

type workItemIdentity struct {
	Provider    string
	Tenant      string
	Project     string
	WorkItemID  string
	ExternalURL string
	StateID     string
}

func getWorkItemIdentity(task storage.Task, payload pipelinePayload) workItemIdentity {
	var ident workItemIdentity

	ident.Provider = task.Source

	if task.ExternalProvider != nil && *task.ExternalProvider != "" {
		ident.Provider = *task.ExternalProvider
	}
	if task.ExternalID != nil && *task.ExternalID != "" {
		ident.WorkItemID = *task.ExternalID
	}
	if task.ExternalUrl != nil && *task.ExternalUrl != "" {
		ident.ExternalURL = *task.ExternalUrl
	}

	if p := strings.TrimSpace(payload.WorkItem["provider"]); p != "" {
		ident.Provider = p
	}
	if id := strings.TrimSpace(payload.WorkItem["id"]); id != "" {
		ident.WorkItemID = id
	}
	if t := strings.TrimSpace(payload.WorkItem["tenant"]); t != "" {
		ident.Tenant = t
	}
	if prj := strings.TrimSpace(payload.WorkItem["project"]); prj != "" {
		ident.Project = prj
	}
	if urlStr := strings.TrimSpace(payload.WorkItem["external_url"]); urlStr != "" {
		ident.ExternalURL = urlStr
	}
	if state := strings.TrimSpace(payload.WorkItem["state_id"]); state != "" {
		ident.StateID = state
	}

	if len(task.ExternalMetadata) > 0 && string(task.ExternalMetadata) != "null" && string(task.ExternalMetadata) != "{}" {
		var extMeta map[string]any
		if err := json.Unmarshal(task.ExternalMetadata, &extMeta); err == nil {
			if p, ok := extMeta["provider"].(string); ok && p != "" && ident.Provider == "" {
				ident.Provider = p
			}
			if id, ok := extMeta["id"].(string); ok && id != "" && ident.WorkItemID == "" {
				ident.WorkItemID = id
			}
			if id, ok := extMeta["work_item_id"].(string); ok && id != "" && ident.WorkItemID == "" {
				ident.WorkItemID = id
			}
			if t, ok := extMeta["tenant"].(string); ok && t != "" && ident.Tenant == "" {
				ident.Tenant = t
			}
			if p, ok := extMeta["project"].(string); ok && p != "" && ident.Project == "" {
				ident.Project = p
			}
			if s, ok := extMeta["state_id"].(string); ok && s != "" && ident.StateID == "" {
				ident.StateID = s
			}
			if u, ok := extMeta["external_url"].(string); ok && u != "" && ident.ExternalURL == "" {
				ident.ExternalURL = u
			}
		}
	}

	return ident
}

func validateWorkItemIdentity(ident workItemIdentity) error {
	if strings.TrimSpace(ident.Provider) == "" || strings.TrimSpace(ident.WorkItemID) == "" {
		return ErrProviderIdentityMissing
	}
	return nil
}

// BuildAuthoringPushInput constructs a push-only model.GenerateInput for the
// second authoring stage. The workspace metadata and provider identity are
// copied from the original task so the workspace agent can locate the slot and
// push the authored content to the remote develop branch. Returns false when
// the task does not qualify as an authoring task.
func BuildAuthoringPushInput(task storage.Task) (model.GenerateInput, bool) {
	input, ok, err := BuildAuthoringPushInputStrict(task)
	if err != nil {
		return model.GenerateInput{}, false
	}
	return input, ok
}

// BuildAuthoringPushInputStrict is the error-returning form used by the
// scheduler so missing provider identity fails explicitly instead of being
// hidden behind a task id fallback.
func BuildAuthoringPushInputStrict(task storage.Task) (model.GenerateInput, bool, error) {
	checkout, ok := isAuthoringTask(task)
	if !ok {
		return model.GenerateInput{}, false, nil
	}

	var p pipelinePayload
	if len(task.Payload) > 0 && string(task.Payload) != "null" && string(task.Payload) != "{}" {
		_ = json.Unmarshal(task.Payload, &p)
	}
	ident := getWorkItemIdentity(task, p)
	if err := validateWorkItemIdentity(ident); err != nil {
		return model.GenerateInput{}, true, err
	}

	return model.GenerateInput{
		Input: PushOnlyCommand,
		Metadata: map[string]string{
			"task_id": task.ID,
			"source":  task.Source,
		},
		WorkspaceMetadata: &model.WorkspaceMetadata{
			Path:         checkout.SlotPath,
			SourceBranch: checkout.SourceBranch,
			Provider:     ident.Provider,
			WorkItemID:   ident.WorkItemID,
			Tenant:       ident.Tenant,
			Project:      ident.Project,
			ExternalURL:  ident.ExternalURL,
			StateID:      ident.StateID,
		},
	}, true, nil
}

// BuildAuthoringGenerateInput constructs a workspace-bound model.GenerateInput
// for Plane-origin authoring tasks. Returns false when the task does not qualify
// as an authoring task (non-Plane source or missing checkout metadata).
func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool) {
	input, ok, err := BuildAuthoringGenerateInputStrict(task)
	if err != nil {
		return model.GenerateInput{}, false
	}
	return input, ok
}

// BuildAuthoringGenerateInputStrict is the error-returning form used by the
// scheduler so a Plane-origin task with checkout metadata but missing provider
// work item identity stops with an explicit failure.
func BuildAuthoringGenerateInputStrict(task storage.Task) (model.GenerateInput, bool, error) {
	checkout, ok := isAuthoringTask(task)
	if !ok {
		return model.GenerateInput{}, false, nil
	}

	// input priority: prompt override > pipeline message > task title
	input := strings.TrimSpace(task.Title)
	instructions := ""

	var p pipelinePayload
	if len(task.Payload) > 0 && string(task.Payload) != "null" && string(task.Payload) != "{}" {
		_ = json.Unmarshal(task.Payload, &p)
	}

	if body := strings.TrimSpace(p.Message); body != "" {
		input = body
	}
	if override := strings.TrimSpace(p.Prompt); override != "" {
		input = override
	}
	instructions = strings.TrimSpace(p.Instructions)

	ident := getWorkItemIdentity(task, p)
	if err := validateWorkItemIdentity(ident); err != nil {
		return model.GenerateInput{}, true, err
	}

	if instructions == "" {
		instructions = defaultAuthoringInstructions
	}

	return model.GenerateInput{
		Input:        input,
		Instructions: instructions,
		Metadata: map[string]string{
			"task_id": task.ID,
			"source":  task.Source,
		},
		WorkspaceMetadata: &model.WorkspaceMetadata{
			Path:         checkout.SlotPath,
			SourceBranch: checkout.SourceBranch,
			Provider:     ident.Provider,
			WorkItemID:   ident.WorkItemID,
			Tenant:       ident.Tenant,
			Project:      ident.Project,
			ExternalURL:  ident.ExternalURL,
			StateID:      ident.StateID,
		},
	}, true, nil
}
