// Package knowledge provides a knowledge capture toolkit for the MCP data platform.
package knowledge

import (
	"fmt"
	"time"
)

// category classifies the type of insight being captured.
type category string

// Valid insight categories.
const (
	categoryCorrection    category = "correction"
	categoryBusinessCtx   category = "business_context"
	categoryDataQuality   category = "data_quality"
	categoryUsageGuidance category = "usage_guidance"
	categoryRelationship  category = "relationship"
	categoryEnhancement   category = "enhancement"
)

// validCategories is the set of accepted category values.
var validCategories = map[category]bool{
	categoryCorrection:    true,
	categoryBusinessCtx:   true,
	categoryDataQuality:   true,
	categoryUsageGuidance: true,
	categoryRelationship:  true,
	categoryEnhancement:   true,
}

// ValidateCategory checks whether a category value is valid.
func ValidateCategory(c string) error {
	if c == "" {
		return fmt.Errorf("category is required and must be one of: correction, business_context, data_quality, usage_guidance, relationship, enhancement")
	}
	if !validCategories[category(c)] {
		return fmt.Errorf("invalid category %q: must be one of: correction, business_context, data_quality, usage_guidance, relationship, enhancement", c)
	}
	return nil
}

// confidence indicates how confident the submitter is in the insight.
type confidence string

// Valid confidence levels.
const (
	confidenceHigh   confidence = "high"
	confidenceMedium confidence = "medium"
	confidenceLow    confidence = "low"
)

// validConfidences is the set of accepted confidence values.
var validConfidences = map[confidence]bool{
	confidenceHigh:   true,
	confidenceMedium: true,
	confidenceLow:    true,
}

// ValidateConfidence checks whether a confidence value is valid.
// An empty string is valid and defaults to "medium".
func ValidateConfidence(c string) error {
	if c == "" {
		return nil
	}
	if !validConfidences[confidence(c)] {
		return fmt.Errorf("invalid confidence %q: must be one of: high, medium, low", c)
	}
	return nil
}

// NormalizeConfidence returns the confidence value, defaulting to "medium" if empty.
func NormalizeConfidence(c string) string {
	if c == "" {
		return string(confidenceMedium)
	}
	return c
}

// source indicates where the insight originated.
type source string

// Valid source values.
const (
	sourceUser           source = "user"
	sourceAgentDiscovery source = "agent_discovery"
	sourceEnrichmentGap  source = "enrichment_gap"
)

// validSources is the set of accepted source values.
var validSources = map[source]bool{
	sourceUser:           true,
	sourceAgentDiscovery: true,
	sourceEnrichmentGap:  true,
}

// ValidateSource checks whether a source value is valid.
// An empty string is valid and defaults to "user".
func ValidateSource(s string) error {
	if s == "" {
		return nil
	}
	if !validSources[source(s)] {
		return fmt.Errorf("invalid source %q: must be one of: user, agent_discovery, enrichment_gap", s)
	}
	return nil
}

// NormalizeSource returns the source value, defaulting to "user" if empty.
func NormalizeSource(s string) string {
	if s == "" {
		return string(sourceUser)
	}
	return s
}

// Insight validation constraints.
const (
	MinInsightTextLen   = 10
	MaxInsightTextLen   = 4000
	MaxEntityURNs       = 10
	MaxRelatedColumns   = 20
	MaxSuggestedActions = 5
	MaxApplyChanges     = 20
	MaxInsightIDs       = 50
)

// ValidateInsightText checks whether the insight text meets length requirements.
func ValidateInsightText(text string) error {
	if text == "" {
		return fmt.Errorf("insight_text is required (minimum %d characters)", MinInsightTextLen)
	}
	if len(text) < MinInsightTextLen {
		return fmt.Errorf("insight_text must be at least %d characters (got %d)", MinInsightTextLen, len(text))
	}
	if len(text) > MaxInsightTextLen {
		return fmt.Errorf("insight_text must be at most %d characters (got %d)", MaxInsightTextLen, len(text))
	}
	return nil
}

// actionType classifies a suggested catalog change.
type actionType string

// Valid action types.
const (
	actionUpdateDescription actionType = "update_description"
	actionAddTag            actionType = "add_tag"
	actionRemoveTag         actionType = "remove_tag"
	actionAddGlossaryTerm   actionType = "add_glossary_term"
	actionFlagQualityIssue  actionType = "flag_quality_issue"
	actionAddDocumentation  actionType = "add_documentation"
	actionAddCuratedQuery   actionType = "add_curated_query"

	// DataHub 1.4.x actions.
	actionSetStructuredProperty    actionType = "set_structured_property"
	actionRemoveStructuredProperty actionType = "remove_structured_property"
	actionRaiseIncident            actionType = "raise_incident"
	actionResolveIncident          actionType = "resolve_incident"

	// Context document actions (DataHub 1.4.x with document support).
	actionAddContextDocument    actionType = "add_context_document"
	actionUpdateContextDocument actionType = "update_context_document"
	actionRemoveContextDocument actionType = "remove_context_document"

	// Platform prompt action.
	actionAddPrompt actionType = "add_prompt"
)

// actionTypeList is a human-readable list of valid action types for error messages.
const actionTypeList = "update_description, add_tag, remove_tag, add_glossary_term, flag_quality_issue, " +
	"add_documentation, add_curated_query, set_structured_property, remove_structured_property, " +
	"raise_incident, resolve_incident, add_context_document, update_context_document, remove_context_document, " +
	"add_prompt"

// validActionTypes is the set of accepted action type values.
var validActionTypes = map[actionType]bool{
	actionUpdateDescription:        true,
	actionAddTag:                   true,
	actionRemoveTag:                true,
	actionAddGlossaryTerm:          true,
	actionFlagQualityIssue:         true,
	actionAddDocumentation:         true,
	actionAddCuratedQuery:          true,
	actionSetStructuredProperty:    true,
	actionRemoveStructuredProperty: true,
	actionRaiseIncident:            true,
	actionResolveIncident:          true,
	actionAddContextDocument:       true,
	actionUpdateContextDocument:    true,
	actionRemoveContextDocument:    true,
	actionAddPrompt:                true,
}

// SuggestedAction represents a proposed catalog change.
type SuggestedAction struct {
	ActionType       string `json:"action_type" example:"update_description"`
	Target           string `json:"target" example:"amount"`
	Detail           string `json:"detail" example:"Gross margin before returns"`
	QuerySQL         string `json:"query_sql,omitempty"`
	QueryDescription string `json:"query_description,omitempty"`
}

// ValidateSuggestedActions validates a slice of suggested actions.
func ValidateSuggestedActions(actions []SuggestedAction) error {
	if len(actions) > MaxSuggestedActions {
		return fmt.Errorf("suggested_actions exceeds maximum of %d (got %d)", MaxSuggestedActions, len(actions))
	}
	for i, a := range actions {
		if !validActionTypes[actionType(a.ActionType)] {
			return fmt.Errorf("suggested_actions[%d]: invalid action_type %q: must be one of: %s", i, a.ActionType, actionTypeList)
		}
		if a.ActionType == string(actionAddCuratedQuery) && a.QuerySQL == "" {
			return fmt.Errorf("suggested_actions[%d]: query_sql is required for add_curated_query", i)
		}
	}
	return nil
}

// RelatedColumn represents a column related to an insight.
type RelatedColumn struct {
	URN       string `json:"urn" example:"urn:li:dataset:(urn:li:dataPlatform:trino,hive.sales.orders,PROD)"`
	Column    string `json:"column" example:"amount"`
	Relevance string `json:"relevance" example:"direct"`
}

// ValidateEntityURNs validates the entity URN slice.
func ValidateEntityURNs(urns []string) error {
	if len(urns) > MaxEntityURNs {
		return fmt.Errorf("entity_urns exceeds maximum of %d (got %d)", MaxEntityURNs, len(urns))
	}
	return nil
}

// ValidateRelatedColumns validates the related columns slice.
func ValidateRelatedColumns(cols []RelatedColumn) error {
	if len(cols) > MaxRelatedColumns {
		return fmt.Errorf("related_columns exceeds maximum of %d (got %d)", MaxRelatedColumns, len(cols))
	}
	return nil
}

// Insight represents a captured domain knowledge insight.
type Insight struct {
	ID               string            `json:"id" example:"a1b2c3d4e5f67890a1b2c3d4e5f67890"`
	CreatedAt        time.Time         `json:"created_at" example:"2026-01-15T14:30:00Z"`
	SessionID        string            `json:"session_id" example:"sess_abc123"`
	CapturedBy       string            `json:"captured_by" example:"analyst@example.com"`
	Persona          string            `json:"persona" example:"analyst"`
	Source           string            `json:"source" example:"user"`
	Category         string            `json:"category" example:"correction"`
	InsightText      string            `json:"insight_text" example:"The amount column represents gross margin before returns, not revenue."`
	Confidence       string            `json:"confidence" example:"high"`
	EntityURNs       []string          `json:"entity_urns"`
	RelatedColumns   []RelatedColumn   `json:"related_columns"`
	SuggestedActions []SuggestedAction `json:"suggested_actions"`
	Status           string            `json:"status" example:"pending"`

	// Lifecycle fields (populated by migrations 000007 and 000008)
	ReviewedBy   string     `json:"reviewed_by,omitempty" example:"admin@example.com"`
	ReviewedAt   *time.Time `json:"reviewed_at,omitempty"`
	ReviewNotes  string     `json:"review_notes,omitempty" example:"Verified with data engineering team"`
	AppliedBy    string     `json:"applied_by,omitempty"`
	AppliedAt    *time.Time `json:"applied_at,omitempty"`
	ChangesetRef string     `json:"changeset_ref,omitempty"`
}

// Insight status constants.
const (
	StatusPending    = "pending"
	StatusApproved   = "approved"
	StatusRejected   = "rejected"
	StatusApplied    = "applied"
	StatusSuperseded = "superseded"
	StatusRolledBack = "rolled_back"
)

// Apply-knowledge action names exposed by the apply_knowledge MCP tool.
const (
	actionBulkReview = "bulk_review"
	actionReview     = "review"
	actionSynthesize = "synthesize"
	actionApply      = "apply"
	actionApprove    = "approve"
	actionReject     = "reject"
)

// validTransitions defines allowed status transitions.
var validTransitions = map[string]map[string]bool{
	StatusPending: {
		StatusApproved:   true,
		StatusRejected:   true,
		StatusSuperseded: true,
	},
	StatusApproved: {
		StatusApplied:  true,
		StatusRejected: true,
	},
	StatusApplied: {
		StatusRolledBack: true,
	},
}

// ValidateStatusTransition checks whether a status transition is allowed.
func ValidateStatusTransition(from, to string) error {
	allowed, ok := validTransitions[from]
	if !ok || !allowed[to] {
		return fmt.Errorf("invalid status transition from %q to %q", from, to)
	}
	return nil
}

// InsightFilter defines filtering criteria for listing insights.
type InsightFilter struct {
	Status     string
	Category   string
	EntityURN  string
	CapturedBy string
	Confidence string
	Source     string
	Since      *time.Time
	Until      *time.Time
	Limit      int
	Offset     int
}

// DefaultLimit is the default page size for list queries.
const DefaultLimit = 20

// MaxLimit is the maximum page size for list queries.
const MaxLimit = 100

// EffectiveLimit returns the limit to use, applying defaults and caps.
func (f *InsightFilter) EffectiveLimit() int {
	if f.Limit <= 0 {
		return DefaultLimit
	}
	if f.Limit > MaxLimit {
		return MaxLimit
	}
	return f.Limit
}

// InsightStats holds aggregated insight statistics.
type InsightStats struct {
	TotalPending int                    `json:"total_pending"`
	ByEntity     []EntityInsightSummary `json:"by_entity"`
	ByCategory   map[string]int         `json:"by_category"`
	ByConfidence map[string]int         `json:"by_confidence"`
	ByStatus     map[string]int         `json:"by_status"`
}

// EntityInsightSummary summarizes insights for a single entity.
type EntityInsightSummary struct {
	EntityURN  string   `json:"entity_urn"`
	Count      int      `json:"count"`
	Categories []string `json:"categories"`
	LatestAt   string   `json:"latest_at"`
}

// InsightUpdate holds fields that can be edited on a non-applied insight.
type InsightUpdate struct {
	InsightText string `json:"insight_text,omitempty"`
	Category    string `json:"category,omitempty"`
	Confidence  string `json:"confidence,omitempty"`
}

// Changeset records a set of changes applied to DataHub from insights.
type Changeset struct {
	ID               string         `json:"id" example:"cs_x1y2z3a4b5c6d7e8"`
	CreatedAt        time.Time      `json:"created_at" example:"2026-01-15T16:00:00Z"`
	TargetURN        string         `json:"target_urn" example:"urn:li:dataset:(urn:li:dataPlatform:trino,hive.sales.orders,PROD)"`
	ChangeType       string         `json:"change_type" example:"update_description"`
	PreviousValue    map[string]any `json:"previous_value"`
	NewValue         map[string]any `json:"new_value"`
	SourceInsightIDs []string       `json:"source_insight_ids"`
	ApprovedBy       string         `json:"approved_by" example:"admin@example.com"`
	AppliedBy        string         `json:"applied_by" example:"admin@example.com"`
	RolledBack       bool           `json:"rolled_back" example:"false"`
	RolledBackBy     string         `json:"rolled_back_by,omitempty"`
	RolledBackAt     *time.Time     `json:"rolled_back_at,omitempty"`
}

// ChangesetFilter defines filtering criteria for listing changesets.
type ChangesetFilter struct {
	EntityURN  string
	AppliedBy  string
	Since      *time.Time
	Until      *time.Time
	RolledBack *bool
	Limit      int
	Offset     int
}

// EffectiveLimit returns the limit to use, applying defaults and caps.
func (f *ChangesetFilter) EffectiveLimit() int {
	if f.Limit <= 0 {
		return DefaultLimit
	}
	if f.Limit > MaxLimit {
		return MaxLimit
	}
	return f.Limit
}

// ApplyChange represents a single change to apply to DataHub.
type ApplyChange struct {
	ChangeType       string `json:"change_type"`
	Target           string `json:"target"`
	Detail           string `json:"detail"`
	QuerySQL         string `json:"query_sql,omitempty"`
	QueryDescription string `json:"query_description,omitempty"`
}

// ValidateApplyChanges validates the changes slice for the apply action.
func ValidateApplyChanges(changes []ApplyChange) error {
	if len(changes) == 0 {
		return fmt.Errorf("changes is required and must not be empty")
	}
	if len(changes) > MaxApplyChanges {
		return fmt.Errorf("changes exceeds maximum of %d (got %d)", MaxApplyChanges, len(changes))
	}
	for i, c := range changes {
		if err := validateApplyChange(c); err != nil {
			return fmt.Errorf("changes[%d]: %w", i, err)
		}
	}
	return nil
}

// validateApplyChange checks a single change for required fields.
func validateApplyChange(c ApplyChange) error {
	if !validActionTypes[actionType(c.ChangeType)] {
		return fmt.Errorf("invalid change_type %q: must be one of: %s", c.ChangeType, actionTypeList)
	}
	return validateChangeRequiredFields(c)
}

// validateChangeRequiredFields checks type-specific required fields for a change.
func validateChangeRequiredFields(c ApplyChange) error {
	switch c.ChangeType {
	case string(actionAddCuratedQuery):
		return requireField(c.QuerySQL, "query_sql is required for add_curated_query")
	case string(actionAddContextDocument):
		if c.Target == "" || c.Detail == "" {
			return fmt.Errorf("target (title) and detail (content) are required for add_context_document")
		}
	case string(actionUpdateContextDocument):
		return requireField(c.Target, "target (document ID) is required for update_context_document")
	case string(actionRemoveContextDocument):
		return requireField(c.Target, "target (document ID) is required for remove_context_document")
	case string(actionAddPrompt):
		return validateAddPromptFields(c)
	}
	return nil
}

// RequiredFieldError is a sentinel-like error for missing required fields.
type RequiredFieldError string

// Error implements the error interface.
func (e RequiredFieldError) Error() string { return string(e) }

// requireField returns an error with the given message if value is empty.
func requireField(value, msg string) error {
	if value == "" {
		return RequiredFieldError(msg)
	}
	return nil
}

// validateAddPromptFields validates required fields for the add_prompt change type.
func validateAddPromptFields(c ApplyChange) error {
	if c.Target == "" {
		return fmt.Errorf("target (prompt name) is required for add_prompt")
	}
	if c.Detail == "" {
		return fmt.Errorf("detail (prompt content) is required for add_prompt")
	}
	return nil
}

// EntityMetadata holds current metadata for an entity from DataHub.
type EntityMetadata struct {
	Description   string   `json:"description"`
	Tags          []string `json:"tags"`
	GlossaryTerms []string `json:"glossary_terms"`
	Owners        []string `json:"owners"`
}

// ProposedChange represents a deterministic change proposal from synthesis.
type ProposedChange struct {
	ChangeType       string   `json:"change_type"`
	Target           string   `json:"target"`
	CurrentValue     string   `json:"current_value"`
	SuggestedValue   string   `json:"suggested_value"`
	SourceInsightIDs []string `json:"source_insight_ids"`
}

// ValidateAction checks whether an action value is valid.
func ValidateAction(action string) error {
	validActions := map[string]bool{
		actionBulkReview: true,
		actionReview:     true,
		actionSynthesize: true,
		actionApply:      true,
		actionApprove:    true,
		actionReject:     true,
	}
	if action == "" {
		return fmt.Errorf("action is required and must be one of: bulk_review, review, synthesize, apply, approve, reject")
	}
	if !validActions[action] {
		return fmt.Errorf("invalid action %q: must be one of: bulk_review, review, synthesize, apply, approve, reject", action)
	}
	return nil
}
