package workitem

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

	"github.com/nomadcode/nomadcode-core/internal/workflow"
)

var ErrInvalidRef = errors.New("invalid workitem ref")

type ProviderID string

type Capability string

const (
	CapabilityLookup           Capability = "lookup"
	CapabilityComment          Capability = "comment"
	CapabilityStatusProjection Capability = "status_projection"
	CapabilityLabelProjection  Capability = "label_projection"
	CapabilityCreate           Capability = "create"
	CapabilityBodyProjection   Capability = "body_projection"
)

type Capabilities map[Capability]bool

func (c Capabilities) Supports(capability Capability) bool {
	return c[capability]
}

type Provider interface {
	ProviderID() ProviderID
	Capabilities() Capabilities
}

type Reader interface {
	FetchWorkItem(ctx context.Context, ref Ref) (WorkItem, error)
}

type Commenter interface {
	AppendComment(ctx context.Context, input CommentInput) error
}

type StatusProjector interface {
	ProjectStatus(ctx context.Context, input StatusProjection) error
}

type Creator interface {
	CreateWorkItem(ctx context.Context, input CreateInput) (CreateResult, error)
}

// BodyProjector updates an existing work item's title and/or body. It is an
// additive projection capability separate from status projection so a body
// rewrite and a status move stay independent steps.
type BodyProjector interface {
	ProjectBody(ctx context.Context, input BodyProjection) error
}

type LabelProjector interface {
	ProjectLabels(ctx context.Context, input LabelProjection) error
}

type Ref struct {
	Provider ProviderID      `json:"provider"`
	Tenant   string          `json:"tenant"`
	Project  string          `json:"project"`
	ID       string          `json:"id"`
	URL      string          `json:"url,omitempty"`
	Metadata json.RawMessage `json:"metadata,omitempty"`
}

type WorkItem struct {
	Ref             Ref             `json:"ref"`
	Title           string          `json:"title"`
	DescriptionText string          `json:"description_text,omitempty"`
	DescriptionHTML string          `json:"description_html,omitempty"`
	State           string          `json:"state,omitempty"`
	AssigneeIDs     []string        `json:"assignee_ids,omitempty"`
	Labels          []string        `json:"labels,omitempty"`
	Metadata        json.RawMessage `json:"metadata,omitempty"`
}

func (w WorkItem) HasAssignee(id string) bool {
	target := strings.TrimSpace(id)
	if target == "" {
		return false
	}
	for _, a := range w.AssigneeIDs {
		if strings.TrimSpace(a) == target {
			return true
		}
	}
	return false
}

type CommentFormat string

const (
	CommentFormatText     CommentFormat = "text"
	CommentFormatMarkdown CommentFormat = "markdown"
	CommentFormatHTML     CommentFormat = "html"
)

type CommentInput struct {
	Ref            Ref           `json:"ref"`
	Body           string        `json:"body"`
	Format         CommentFormat `json:"format"`
	ExternalSource string        `json:"external_source,omitempty"`
	ExternalID     string        `json:"external_id,omitempty"`
	Visibility     string        `json:"visibility,omitempty"`
}

type StatusProjection struct {
	Ref            Ref    `json:"ref"`
	CanonicalState string `json:"canonical_state"`
	ProviderState  string `json:"provider_state"`
}

type BodyProjection struct {
	Ref             Ref    `json:"ref"`
	Title           string `json:"title,omitempty"`
	DescriptionHTML string `json:"description_html,omitempty"`
}

type CreateInput struct {
	Provider        ProviderID `json:"provider"`
	Tenant          string     `json:"tenant"`
	Project         string     `json:"project"`
	Title           string     `json:"title"`
	DescriptionText string     `json:"description_text,omitempty"`
	DescriptionHTML string     `json:"description_html,omitempty"`
}

type CreateResult struct {
	Ref Ref `json:"ref"`
}

type LabelTarget struct {
	Canonical string `json:"canonical"`
	Provider  string `json:"provider"`
}

type LabelProjection struct {
	Ref    Ref           `json:"ref"`
	Add    []LabelTarget `json:"add,omitempty"`
	Remove []LabelTarget `json:"remove,omitempty"`
}

type Mapping struct {
	States map[string]string `json:"states,omitempty"`
	Labels map[string]string `json:"labels,omitempty"`
}

func (m Mapping) StateTarget(canonical string) (string, bool) {
	target := strings.TrimSpace(m.States[strings.TrimSpace(canonical)])
	return target, target != ""
}

func (m Mapping) LabelTarget(canonical string) (string, bool) {
	target := strings.TrimSpace(m.Labels[strings.TrimSpace(canonical)])
	return target, target != ""
}

type TaskCreateInput struct {
	Ref     Ref
	StateID string
	Comment string
}

func firstNonEmpty(ss ...string) string {
	for _, s := range ss {
		if trimmed := strings.TrimSpace(s); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func BuildCreateTaskInput(input TaskCreateInput, item WorkItem) (workflow.CreateTaskInput, error) {
	ref, err := NormalizeRef(input.Ref)
	if err != nil {
		return workflow.CreateTaskInput{}, err
	}

	title := strings.TrimSpace(item.Title)
	if title == "" {
		title = ref.ID
	}

	message := firstNonEmpty(input.Comment, item.DescriptionText, item.DescriptionHTML, title)

	metadata := map[string]string{
		"provider":     string(ref.Provider),
		"tenant":       ref.Tenant,
		"project":      ref.Project,
		"id":           ref.ID,
		"state_id":     strings.TrimSpace(input.StateID),
		"external_url": ref.URL,
	}

	payloadBytes, err := json.Marshal(map[string]any{
		"message":   message,
		"work_item": metadata,
	})
	if err != nil {
		return workflow.CreateTaskInput{}, err
	}

	metadataBytes, err := json.Marshal(metadata)
	if err != nil {
		return workflow.CreateTaskInput{}, err
	}

	return workflow.CreateTaskInput{
		Title:   title,
		Source:  string(ref.Provider),
		Payload: payloadBytes,
		External: &workflow.ExternalRefInput{
			Provider: string(ref.Provider),
			ID:       ref.ID,
			URL:      ref.URL,
			Metadata: metadataBytes,
		},
	}, nil
}

func NormalizeRef(ref Ref) (Ref, error) {
	provider := ProviderID(strings.TrimSpace(string(ref.Provider)))
	id := strings.TrimSpace(ref.ID)
	if provider == "" || id == "" {
		return Ref{}, ErrInvalidRef
	}

	meta := ref.Metadata
	if len(meta) == 0 {
		meta = json.RawMessage("{}")
	} else if !json.Valid(meta) {
		return Ref{}, ErrInvalidRef
	}

	return Ref{
		Provider: provider,
		Tenant:   strings.TrimSpace(ref.Tenant),
		Project:  strings.TrimSpace(ref.Project),
		ID:       id,
		URL:      strings.TrimSpace(ref.URL),
		Metadata: meta,
	}, nil
}
