package models

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
	"time"
)

// ── Workflow ────────────────────────────────

// Workflow is a team-owned or global staged process definition.
type Workflow struct {
	ID          string          `json:"id"`
	TeamID      *string         `json:"team_id,omitempty"`
	Name        string          `json:"name"`
	Slug        string          `json:"slug"`
	Description string          `json:"description"`
	Branding    json.RawMessage `json:"branding"`
	EntryMode   string          `json:"entry_mode"` // public_link | team_only
	IsActive    bool            `json:"is_active"`
	Version     int             `json:"version"`
	OnComplete    json.RawMessage `json:"on_complete,omitempty"`
	Retention     json.RawMessage `json:"retention"`
	WebhookURL              string          `json:"webhook_url,omitempty"`
	WebhookSecret           string          `json:"webhook_secret,omitempty"`
	StalenessTimeoutHours   *int            `json:"staleness_timeout_hours,omitempty"`
	CreatedBy               string          `json:"created_by"`
	CreatedAt   time.Time       `json:"created_at"`
	UpdatedAt   time.Time       `json:"updated_at"`

	// Joined data (not stored directly)
	Stages []WorkflowStage `json:"stages,omitempty"`
}

// WorkflowPatch contains optional fields for updating a workflow.
type WorkflowPatch struct {
	Name        *string          `json:"name,omitempty"`
	Description *string          `json:"description,omitempty"`
	TeamID      *string          `json:"team_id,omitempty"`
	Branding    *json.RawMessage `json:"branding,omitempty"`
	EntryMode   *string          `json:"entry_mode,omitempty"`
	IsActive    *bool            `json:"is_active,omitempty"`
	OnComplete  *json.RawMessage `json:"on_complete,omitempty"`
	Retention   *json.RawMessage `json:"retention,omitempty"`
	WebhookURL              *string          `json:"webhook_url,omitempty"`
	WebhookSecret           *string          `json:"webhook_secret,omitempty"`
	StalenessTimeoutHours   *int             `json:"staleness_timeout_hours,omitempty"`
}

// ── Workflow Stage ──────────────────────────

// WorkflowStage is an ordered step within a workflow definition.
type WorkflowStage struct {
	ID               string          `json:"id"`
	WorkflowID       string          `json:"workflow_id"`
	Ordinal          int             `json:"ordinal"`
	Name             string          `json:"name"`
	AssignmentTeamID *string         `json:"assignment_team_id,omitempty"`
	FormTemplate     json.RawMessage `json:"form_template"`
	StageMode        string          `json:"stage_mode"`  // form | review | delegated | automated
	Audience         string          `json:"audience"`    // team | public | system
	StageType        string          `json:"stage_type"`  // simple | dynamic | automated
	AutoTransition   bool            `json:"auto_transition"`
	StageConfig      json.RawMessage `json:"stage_config"`
	BranchRules      json.RawMessage `json:"branch_rules"`
	StarlarkHook     *string         `json:"starlark_hook,omitempty"`
	SurfacePkgID     *string         `json:"surface_pkg_id,omitempty"`
	SLASeconds       *int            `json:"sla_seconds,omitempty"`
	CreatedAt        time.Time       `json:"created_at"`
}

// ── Stage Mode Constants ────────────────────

const (
	StageModeForm      = "form"
	StageModeReview    = "review"
	StageModeDelegated = "delegated"
	StageModeAutomated = "automated"
)

// ValidStageModes is the set of valid stage_mode values.
var ValidStageModes = map[string]bool{
	StageModeForm:      true,
	StageModeReview:    true,
	StageModeDelegated: true,
	StageModeAutomated: true,
}

// ── Stage Type Constants ────────────────────

const (
	StageTypeSimple    = "simple"
	StageTypeDynamic   = "dynamic"
	StageTypeAutomated = "automated"
)

// ValidStageTypes is the set of valid stage_type values.
var ValidStageTypes = map[string]bool{
	StageTypeSimple:    true,
	StageTypeDynamic:   true,
	StageTypeAutomated: true,
}

// ── Audience Constants ──────────────────────

const (
	AudienceTeam   = "team"
	AudiencePublic = "public"
	AudienceSystem = "system"
)

// ValidAudiences is the set of valid audience values.
var ValidAudiences = map[string]bool{
	AudienceTeam:   true,
	AudiencePublic: true,
	AudienceSystem: true,
}

// ── Typed Form Template ─────────────────────

// TypedFormTemplate is the structured form_template schema (v0.29.3).
// v0.35.0: added Fieldsets for progressive multi-step forms.
type TypedFormTemplate struct {
	Fields    []FormField    `json:"fields"`
	Fieldsets []FormFieldset `json:"fieldsets,omitempty"` // v0.35.0: multi-step form groups
	Hooks     *FormHooks     `json:"hooks,omitempty"`
}

// FormFieldset groups fields into a labelled step for progressive forms (v0.35.0).
// When fieldsets is present, top-level fields is ignored.
type FormFieldset struct {
	Label  string      `json:"label"`
	Fields []FormField `json:"fields"`
}

// FieldCondition controls conditional visibility of a form field (v0.35.0).
// When set, the field is shown/validated only if the condition is met.
type FieldCondition struct {
	When  string `json:"when"`            // key of the field to check
	Op    string `json:"op,omitempty"`    // eq (default) | neq | in | exists
	Value any    `json:"value,omitempty"` // value to compare against
}

// FormField is a single field in a typed form template.
type FormField struct {
	Key         string           `json:"key"`
	Type        string           `json:"type"` // text | email | select | number | date | textarea | checkbox | file
	Label       string           `json:"label"`
	Placeholder string           `json:"placeholder,omitempty"`
	Required    bool             `json:"required,omitempty"`
	Default     interface{}      `json:"default,omitempty"`
	Options     []FormOption     `json:"options,omitempty"`
	Validation  *FormValidation  `json:"validation,omitempty"`
	Condition   *FieldCondition  `json:"condition,omitempty"` // v0.35.0: conditional visibility
}

// FormOption is a choice for select fields.
type FormOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

// FormValidation holds type-specific validation rules.
type FormValidation struct {
	MinLength *int     `json:"min_length,omitempty"` // text, textarea, email
	MaxLength *int     `json:"max_length,omitempty"` // text, textarea, email
	Pattern   string   `json:"pattern,omitempty"`    // text, email (regex)
	Min       *float64 `json:"min,omitempty"`        // number
	Max       *float64 `json:"max,omitempty"`        // number
	Step      *float64 `json:"step,omitempty"`       // number
	MinDate   string   `json:"min_date,omitempty"`   // date (YYYY-MM-DD)
	MaxDate   string   `json:"max_date,omitempty"`   // date (YYYY-MM-DD)
	Accept    string   `json:"accept,omitempty"`     // file (MIME types)
	MaxSize   *int64   `json:"max_size_bytes,omitempty"` // file
}

// FormHooks wires Starlark validation/submission hooks for a stage's form.
type FormHooks struct {
	PackageID string `json:"package_id"`
	Validate  string `json:"validate,omitempty"`  // entry point name
	OnSubmit  string `json:"on_submit,omitempty"` // entry point name
}

// ValidFormFieldTypes is the set of recognized field types.
var ValidFormFieldTypes = map[string]bool{
	"text": true, "email": true, "select": true, "number": true,
	"date": true, "textarea": true, "checkbox": true, "file": true,
}

// ParseTypedFormTemplate parses a raw JSON form_template into the typed schema.
// Returns nil if the template is empty, legacy format, or not a typed template.
func ParseTypedFormTemplate(raw json.RawMessage) *TypedFormTemplate {
	if len(raw) == 0 || string(raw) == "{}" || string(raw) == "null" {
		return nil
	}
	var tpl TypedFormTemplate
	if err := json.Unmarshal(raw, &tpl); err != nil {
		return nil
	}

	// v0.35.0: If fieldsets present, flatten into fields for backward compat
	if len(tpl.Fieldsets) > 0 {
		var allFields []FormField
		for _, fs := range tpl.Fieldsets {
			allFields = append(allFields, fs.Fields...)
		}
		if len(allFields) > 0 {
			tpl.Fields = allFields
			return &tpl
		}
	}

	if len(tpl.Fields) == 0 {
		return nil
	}
	// Verify this is actually the typed format (first field must have key+type)
	if tpl.Fields[0].Key == "" || tpl.Fields[0].Type == "" {
		return nil
	}
	return &tpl
}

// AllFields returns all fields from the template, whether from top-level fields or fieldsets.
func (t *TypedFormTemplate) AllFields() []FormField {
	return t.Fields // ParseTypedFormTemplate already flattens fieldsets into Fields
}

// FieldError is a validation error for a specific form field.
type FieldError struct {
	Key     string `json:"key"`
	Message string `json:"message"`
}

// ValidateFormData validates submitted data against a typed form template.
func ValidateFormData(tpl *TypedFormTemplate, data map[string]interface{}) []FieldError {
	var errs []FieldError
	for _, f := range tpl.Fields {
		// v0.35.0: Skip fields whose condition is not met
		if f.Condition != nil && !evaluateFieldCondition(f.Condition, data) {
			continue
		}

		val, present := data[f.Key]

		// Required check
		if f.Required && (!present || val == nil || val == "") {
			errs = append(errs, FieldError{Key: f.Key, Message: f.Label + " is required"})
			continue
		}
		if !present || val == nil {
			continue
		}

		switch f.Type {
		case "text", "textarea":
			errs = append(errs, validateString(f, val)...)
		case "email":
			errs = append(errs, validateEmail(f, val)...)
		case "number":
			errs = append(errs, validateNumber(f, val)...)
		case "date":
			errs = append(errs, validateDate(f, val)...)
		case "select":
			errs = append(errs, validateSelect(f, val)...)
		case "checkbox":
			if _, ok := val.(bool); !ok {
				errs = append(errs, FieldError{Key: f.Key, Message: f.Label + " must be true or false"})
			}
		}
	}
	return errs
}

func validateString(f FormField, val interface{}) []FieldError {
	s, ok := val.(string)
	if !ok {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be text"}}
	}
	var errs []FieldError
	v := f.Validation
	if v == nil {
		return nil
	}
	if v.MinLength != nil && len(s) < *v.MinLength {
		errs = append(errs, FieldError{Key: f.Key, Message: fmt.Sprintf("%s must be at least %d characters", f.Label, *v.MinLength)})
	}
	if v.MaxLength != nil && len(s) > *v.MaxLength {
		errs = append(errs, FieldError{Key: f.Key, Message: fmt.Sprintf("%s must be at most %d characters", f.Label, *v.MaxLength)})
	}
	if v.Pattern != "" {
		if re, err := regexp.Compile(v.Pattern); err == nil && !re.MatchString(s) {
			errs = append(errs, FieldError{Key: f.Key, Message: f.Label + " does not match the required pattern"})
		}
	}
	return errs
}

var emailRe = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)

func validateEmail(f FormField, val interface{}) []FieldError {
	s, ok := val.(string)
	if !ok {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be text"}}
	}
	if !emailRe.MatchString(s) {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a valid email address"}}
	}
	var errs []FieldError
	if f.Validation != nil {
		if f.Validation.Pattern != "" {
			if re, err := regexp.Compile(f.Validation.Pattern); err == nil && !re.MatchString(s) {
				errs = append(errs, FieldError{Key: f.Key, Message: f.Label + " does not match the required pattern"})
			}
		}
	}
	return errs
}

func validateNumber(f FormField, val interface{}) []FieldError {
	var n float64
	switch v := val.(type) {
	case float64:
		n = v
	case int:
		n = float64(v)
	case json.Number:
		var err error
		n, err = v.Float64()
		if err != nil {
			return []FieldError{{Key: f.Key, Message: f.Label + " must be a number"}}
		}
	case string:
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a number"}}
	default:
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a number"}}
	}
	var errs []FieldError
	if f.Validation != nil {
		if f.Validation.Min != nil && n < *f.Validation.Min {
			errs = append(errs, FieldError{Key: f.Key, Message: fmt.Sprintf("%s must be at least %g", f.Label, *f.Validation.Min)})
		}
		if f.Validation.Max != nil && n > *f.Validation.Max {
			errs = append(errs, FieldError{Key: f.Key, Message: fmt.Sprintf("%s must be at most %g", f.Label, *f.Validation.Max)})
		}
	}
	return errs
}

func validateDate(f FormField, val interface{}) []FieldError {
	s, ok := val.(string)
	if !ok {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a date string (YYYY-MM-DD)"}}
	}
	_, err := time.Parse("2006-01-02", s)
	if err != nil {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a valid date (YYYY-MM-DD)"}}
	}
	if f.Validation != nil {
		if f.Validation.MinDate != "" {
			if minD, e := time.Parse("2006-01-02", f.Validation.MinDate); e == nil {
				if d, _ := time.Parse("2006-01-02", s); d.Before(minD) {
					return []FieldError{{Key: f.Key, Message: fmt.Sprintf("%s must be on or after %s", f.Label, f.Validation.MinDate)}}
				}
			}
		}
		if f.Validation.MaxDate != "" {
			if maxD, e := time.Parse("2006-01-02", f.Validation.MaxDate); e == nil {
				if d, _ := time.Parse("2006-01-02", s); d.After(maxD) {
					return []FieldError{{Key: f.Key, Message: fmt.Sprintf("%s must be on or before %s", f.Label, f.Validation.MaxDate)}}
				}
			}
		}
	}
	return nil
}

func validateSelect(f FormField, val interface{}) []FieldError {
	s, ok := val.(string)
	if !ok {
		return []FieldError{{Key: f.Key, Message: f.Label + " must be a string"}}
	}
	if len(f.Options) == 0 {
		return nil
	}
	for _, o := range f.Options {
		if strings.EqualFold(o.Value, s) {
			return nil
		}
	}
	return []FieldError{{Key: f.Key, Message: f.Label + " is not a valid option"}}
}

// ── Field Condition Evaluator (v0.35.0) ─────

// evaluateFieldCondition checks if a conditional field should be visible/validated.
func evaluateFieldCondition(cond *FieldCondition, data map[string]interface{}) bool {
	val, exists := data[cond.When]
	op := cond.Op
	if op == "" {
		op = "eq"
	}

	switch op {
	case "exists":
		return exists
	case "eq":
		if !exists {
			return false
		}
		return fmt.Sprintf("%v", val) == fmt.Sprintf("%v", cond.Value)
	case "neq":
		if !exists {
			return true
		}
		return fmt.Sprintf("%v", val) != fmt.Sprintf("%v", cond.Value)
	case "in":
		if !exists {
			return false
		}
		arr, ok := cond.Value.([]interface{})
		if !ok {
			return false
		}
		vs := fmt.Sprintf("%v", val)
		for _, item := range arr {
			if fmt.Sprintf("%v", item) == vs {
				return true
			}
		}
		return false
	default:
		return true
	}
}

// ── Instance Status Constants ───────────────

const (
	InstanceStatusActive    = "active"
	InstanceStatusCompleted = "completed"
	InstanceStatusCancelled = "cancelled"
	InstanceStatusStale     = "stale"
	InstanceStatusError     = "error"
)

// ValidInstanceStatuses is the set of valid workflow instance status values.
var ValidInstanceStatuses = map[string]bool{
	InstanceStatusActive:    true,
	InstanceStatusCompleted: true,
	InstanceStatusCancelled: true,
	InstanceStatusStale:     true,
	InstanceStatusError:     true,
}

// ── Assignment Status Constants ─────────────

const (
	AssignmentStatusUnassigned = "unassigned"
	AssignmentStatusClaimed    = "claimed"
	AssignmentStatusCompleted  = "completed"
	AssignmentStatusCancelled  = "cancelled"
)

// ValidAssignmentStatuses is the set of valid workflow assignment status values.
var ValidAssignmentStatuses = map[string]bool{
	AssignmentStatusUnassigned: true,
	AssignmentStatusClaimed:    true,
	AssignmentStatusCompleted:  true,
	AssignmentStatusCancelled:  true,
}

// ── Workflow Instance ───────────────────────

// WorkflowInstance tracks a single execution of a workflow definition.
type WorkflowInstance struct {
	ID              string          `json:"id"`
	WorkflowID      string          `json:"workflow_id"`
	WorkflowVersion int             `json:"workflow_version"`
	CurrentStage    string          `json:"current_stage"`
	StageData       json.RawMessage `json:"stage_data"`
	Status          string          `json:"status"`
	StartedBy       string          `json:"started_by"`
	EntryToken      *string         `json:"entry_token,omitempty"`
	Metadata        json.RawMessage `json:"metadata"`
	StageEnteredAt  time.Time       `json:"stage_entered_at"`
	CreatedAt       time.Time       `json:"created_at"`
	UpdatedAt       time.Time       `json:"updated_at"`
}

// ── Workflow Assignment ─────────────────────

// WorkflowAssignment tracks per-stage work items in the team queue.
type WorkflowAssignment struct {
	ID          string          `json:"id"`
	InstanceID  string          `json:"instance_id"`
	Stage       string          `json:"stage"`
	TeamID      string          `json:"team_id"`
	AssignedTo  *string         `json:"assigned_to,omitempty"`
	Status      string          `json:"status"`
	ReviewData  json.RawMessage `json:"review_data"`
	ClaimedAt   *time.Time      `json:"claimed_at,omitempty"`
	CompletedAt *time.Time      `json:"completed_at,omitempty"`
	CreatedAt   time.Time       `json:"created_at"`
}

// ── Workflow Signoff (v0.3.4) ───────────────

// WorkflowSignoff records a user's approval or rejection at a stage boundary.
type WorkflowSignoff struct {
	ID         string    `json:"id"`
	InstanceID string    `json:"instance_id"`
	Stage      string    `json:"stage"`
	UserID     string    `json:"user_id"`
	Decision   string    `json:"decision"` // approve | reject
	Comment    string    `json:"comment"`
	CreatedAt  time.Time `json:"created_at"`
}

// Signoff decision constants.
const (
	SignoffApprove = "approve"
	SignoffReject  = "reject"
)

// ── Workflow Version (immutable snapshot) ───

// WorkflowVersion is an immutable snapshot of a workflow definition
// at a point in time. Active workflow instances run against a pinned version.
type WorkflowVersion struct {
	ID            string          `json:"id"`
	WorkflowID    string          `json:"workflow_id"`
	VersionNumber int             `json:"version_number"`
	Snapshot      json.RawMessage `json:"snapshot"`
	CreatedAt     time.Time       `json:"created_at"`
}
