// Package models contains domain models for claude-mnemonic.
package models

import (
	"strings"
	"time"
)

// RelationType represents the type of relationship between observations.
type RelationType string

const (
	// RelationCauses means source observation caused target observation.
	// Example: "This architectural decision caused this bug"
	RelationCauses RelationType = "causes"
	// RelationFixes means source observation fixes target observation.
	// Example: "This bugfix addresses that discovered issue"
	RelationFixes RelationType = "fixes"
	// RelationSupersedes means source observation supersedes target observation.
	// Example: "This new approach replaces the old workaround"
	RelationSupersedes RelationType = "supersedes"
	// RelationDependsOn means source observation depends on target observation.
	// Example: "This feature relies on that architectural decision"
	RelationDependsOn RelationType = "depends_on"
	// RelationRelatesTo means observations are related but no causal relationship.
	// Example: "Both deal with authentication"
	RelationRelatesTo RelationType = "relates_to"
	// RelationEvolvesFrom means source observation evolved from target observation.
	// Example: "This refined pattern evolved from that initial discovery"
	RelationEvolvesFrom RelationType = "evolves_from"
)

// AllRelationTypes is the list of all valid relation types.
var AllRelationTypes = []RelationType{
	RelationCauses,
	RelationFixes,
	RelationSupersedes,
	RelationDependsOn,
	RelationRelatesTo,
	RelationEvolvesFrom,
}

// RelationDetectionSource indicates how a relationship was detected.
type RelationDetectionSource string

const (
	// DetectionSourceFileOverlap means relationship was detected via shared file references.
	DetectionSourceFileOverlap RelationDetectionSource = "file_overlap"
	// DetectionSourceEmbeddingSimilarity means relationship was detected via vector similarity.
	DetectionSourceEmbeddingSimilarity RelationDetectionSource = "embedding_similarity"
	// DetectionSourceTemporalProximity means relationship was detected via close timestamps.
	DetectionSourceTemporalProximity RelationDetectionSource = "temporal_proximity"
	// DetectionSourceNarrativeMention means relationship was detected via explicit mentions.
	DetectionSourceNarrativeMention RelationDetectionSource = "narrative_mention"
	// DetectionSourceConceptOverlap means relationship was detected via shared concepts.
	DetectionSourceConceptOverlap RelationDetectionSource = "concept_overlap"
	// DetectionSourceTypeProgression means relationship was detected via type progression pattern.
	DetectionSourceTypeProgression RelationDetectionSource = "type_progression"
)

// ObservationRelation represents a directed relationship between two observations.
type ObservationRelation struct {
	RelationType    RelationType            `db:"relation_type" json:"relation_type"`
	DetectionSource RelationDetectionSource `db:"detection_source" json:"detection_source"`
	Reason          string                  `db:"reason" json:"reason,omitempty"`
	CreatedAt       string                  `db:"created_at" json:"created_at"`
	ID              int64                   `db:"id" json:"id"`
	SourceID        int64                   `db:"source_id" json:"source_id"`
	TargetID        int64                   `db:"target_id" json:"target_id"`
	Confidence      float64                 `db:"confidence" json:"confidence"`
	CreatedAtEpoch  int64                   `db:"created_at_epoch" json:"created_at_epoch"`
}

// NewObservationRelation creates a new observation relation.
func NewObservationRelation(sourceID, targetID int64, relType RelationType, confidence float64, source RelationDetectionSource, reason string) *ObservationRelation {
	now := time.Now()
	return &ObservationRelation{
		SourceID:        sourceID,
		TargetID:        targetID,
		RelationType:    relType,
		Confidence:      confidence,
		DetectionSource: source,
		Reason:          reason,
		CreatedAt:       now.Format(time.RFC3339),
		CreatedAtEpoch:  now.UnixMilli(),
	}
}

// RelationDetectionResult contains the result of relation detection.
type RelationDetectionResult struct {
	RelationType    RelationType
	DetectionSource RelationDetectionSource
	Reason          string
	SourceID        int64
	TargetID        int64
	Confidence      float64
}

// DetectFileOverlapRelation checks if observations share file references and determines relationship type.
func DetectFileOverlapRelation(newer, older *Observation) *RelationDetectionResult {
	// Check for overlapping modified files
	newerModified := make(map[string]bool)
	for _, f := range newer.FilesModified {
		newerModified[f] = true
	}

	olderModified := make(map[string]bool)
	for _, f := range older.FilesModified {
		olderModified[f] = true
	}

	// Files modified by both
	var sharedModified []string
	for f := range newerModified {
		if olderModified[f] {
			sharedModified = append(sharedModified, f)
		}
	}

	// Files that newer reads which older modified
	var newerReadsOlderModified []string
	for _, f := range newer.FilesRead {
		if olderModified[f] {
			newerReadsOlderModified = append(newerReadsOlderModified, f)
		}
	}

	// Calculate overlap score
	overlap := len(sharedModified) + len(newerReadsOlderModified)
	if overlap == 0 {
		return nil
	}

	// Determine relationship type based on observation types and file overlap
	relType := RelationRelatesTo
	confidence := 0.5 + float64(overlap)*0.1 // Base 0.5, +0.1 per overlapping file

	// Type-based relationship inference
	switch {
	case newer.Type == ObsTypeBugfix && (older.Type == ObsTypeDecision || older.Type == ObsTypeFeature):
		relType = RelationFixes
		confidence += 0.2
	case newer.Type == ObsTypeRefactor && older.Type == ObsTypeDiscovery:
		relType = RelationEvolvesFrom
		confidence += 0.15
	case newer.Type == older.Type && len(sharedModified) > 0:
		relType = RelationSupersedes
		confidence += 0.1
	case newer.Type == ObsTypeFeature && older.Type == ObsTypeDecision:
		relType = RelationDependsOn
		confidence += 0.15
	}

	if confidence > 1.0 {
		confidence = 1.0
	}

	reason := buildFileOverlapReason(sharedModified, newerReadsOlderModified)

	return &RelationDetectionResult{
		SourceID:        newer.ID,
		TargetID:        older.ID,
		RelationType:    relType,
		Confidence:      confidence,
		DetectionSource: DetectionSourceFileOverlap,
		Reason:          reason,
	}
}

// buildFileOverlapReason creates a human-readable reason for file overlap relation.
func buildFileOverlapReason(shared, readsModified []string) string {
	parts := []string{}
	if len(shared) > 0 {
		parts = append(parts, "both modified: "+strings.Join(truncateList(shared, 3), ", "))
	}
	if len(readsModified) > 0 {
		parts = append(parts, "reads files modified by older: "+strings.Join(truncateList(readsModified, 3), ", "))
	}
	return strings.Join(parts, "; ")
}

// DetectConceptOverlapRelation checks if observations share concepts.
func DetectConceptOverlapRelation(newer, older *Observation) *RelationDetectionResult {
	newerConcepts := make(map[string]bool)
	for _, c := range newer.Concepts {
		newerConcepts[c] = true
	}

	var shared []string
	for _, c := range older.Concepts {
		if newerConcepts[c] {
			shared = append(shared, c)
		}
	}

	if len(shared) == 0 {
		return nil
	}

	// Calculate confidence based on overlap ratio
	totalUniqueConcepts := len(newerConcepts)
	for _, c := range older.Concepts {
		if !newerConcepts[c] {
			totalUniqueConcepts++
		}
	}

	overlapRatio := float64(len(shared)) / float64(totalUniqueConcepts)
	confidence := 0.3 + overlapRatio*0.5 // Base 0.3, scale with overlap

	// Boost for important concepts
	for _, c := range shared {
		if isHighValueConcept(c) {
			confidence += 0.1
		}
	}
	if confidence > 1.0 {
		confidence = 1.0
	}

	return &RelationDetectionResult{
		SourceID:        newer.ID,
		TargetID:        older.ID,
		RelationType:    RelationRelatesTo,
		Confidence:      confidence,
		DetectionSource: DetectionSourceConceptOverlap,
		Reason:          "shared concepts: " + strings.Join(truncateList(shared, 5), ", "),
	}
}

// isHighValueConcept returns true for concepts that strongly indicate relationships.
func isHighValueConcept(concept string) bool {
	highValue := map[string]bool{
		"security":       true,
		"architecture":   true,
		"gotcha":         true,
		"anti-pattern":   true,
		"best-practice":  true,
		"error-handling": true,
	}
	return highValue[concept]
}

// DetectTypeProgressionRelation checks for natural type progressions.
// Example: discovery -> decision -> feature -> bugfix
func DetectTypeProgressionRelation(newer, older *Observation) *RelationDetectionResult {
	// Define natural type progressions
	progressions := map[ObservationType][]ObservationType{
		ObsTypeBugfix:   {ObsTypeDiscovery, ObsTypeFeature, ObsTypeDecision},
		ObsTypeFeature:  {ObsTypeDiscovery, ObsTypeDecision},
		ObsTypeRefactor: {ObsTypeDiscovery, ObsTypeFeature, ObsTypeBugfix},
		ObsTypeDecision: {ObsTypeDiscovery},
		ObsTypeChange:   {ObsTypeDiscovery, ObsTypeDecision},
	}

	validPredecessors, ok := progressions[newer.Type]
	if !ok {
		return nil
	}

	isValidProgression := false
	for _, pred := range validPredecessors {
		if older.Type == pred {
			isValidProgression = true
			break
		}
	}

	if !isValidProgression {
		return nil
	}

	// Determine relationship type based on progression
	var relType RelationType
	var confidence float64 = 0.4

	switch {
	case newer.Type == ObsTypeBugfix && older.Type == ObsTypeDiscovery:
		relType = RelationFixes
		confidence = 0.6
	case newer.Type == ObsTypeBugfix && older.Type == ObsTypeFeature:
		relType = RelationFixes
		confidence = 0.5
	case newer.Type == ObsTypeFeature && older.Type == ObsTypeDecision:
		relType = RelationDependsOn
		confidence = 0.6
	case newer.Type == ObsTypeRefactor:
		relType = RelationEvolvesFrom
		confidence = 0.5
	default:
		relType = RelationRelatesTo
	}

	return &RelationDetectionResult{
		SourceID:        newer.ID,
		TargetID:        older.ID,
		RelationType:    relType,
		Confidence:      confidence,
		DetectionSource: DetectionSourceTypeProgression,
		Reason:          string(older.Type) + " -> " + string(newer.Type) + " progression",
	}
}

// DetectTemporalProximityRelation checks if observations are temporally close (same session).
func DetectTemporalProximityRelation(newer, older *Observation) *RelationDetectionResult {
	// Only relate observations from the same session
	if newer.SDKSessionID != older.SDKSessionID {
		return nil
	}

	// Check temporal proximity (within 5 minutes)
	timeDiffMs := newer.CreatedAtEpoch - older.CreatedAtEpoch
	if timeDiffMs < 0 {
		timeDiffMs = -timeDiffMs
	}

	fiveMinutesMs := int64(5 * 60 * 1000)
	if timeDiffMs > fiveMinutesMs {
		return nil
	}

	// Calculate confidence based on temporal proximity
	// Closer = higher confidence
	proximityRatio := 1.0 - (float64(timeDiffMs) / float64(fiveMinutesMs))
	confidence := 0.3 + proximityRatio*0.4

	return &RelationDetectionResult{
		SourceID:        newer.ID,
		TargetID:        older.ID,
		RelationType:    RelationRelatesTo,
		Confidence:      confidence,
		DetectionSource: DetectionSourceTemporalProximity,
		Reason:          "same session, close timestamps",
	}
}

// NarrativeMentionPatterns are patterns that indicate explicit relationships in narratives.
var NarrativeMentionPatterns = []struct {
	Pattern      string
	RelationType RelationType
	ConfBoost    float64
}{
	{" caused ", RelationCauses, 0.3},
	{" causes ", RelationCauses, 0.3},
	{" because of ", RelationCauses, 0.25},
	{" due to ", RelationCauses, 0.2},
	{" fixes ", RelationFixes, 0.3},
	{" fixed ", RelationFixes, 0.3},
	{" resolves ", RelationFixes, 0.3},
	{" addresses ", RelationFixes, 0.25},
	{" replaces ", RelationSupersedes, 0.3},
	{" supersedes ", RelationSupersedes, 0.35},
	{" instead of ", RelationSupersedes, 0.25},
	{" depends on ", RelationDependsOn, 0.3},
	{" requires ", RelationDependsOn, 0.25},
	{" builds on ", RelationDependsOn, 0.25},
	{" based on ", RelationDependsOn, 0.2},
	{" related to ", RelationRelatesTo, 0.2},
	{" similar to ", RelationRelatesTo, 0.2},
	{" evolved from ", RelationEvolvesFrom, 0.3},
	{" improved from ", RelationEvolvesFrom, 0.25},
	{" refined from ", RelationEvolvesFrom, 0.25},
}

// DetectNarrativeMentionRelation checks if newer observation's narrative mentions relationship.
func DetectNarrativeMentionRelation(newer, older *Observation) *RelationDetectionResult {
	if !newer.Narrative.Valid || newer.Narrative.String == "" {
		return nil
	}

	narrative := strings.ToLower(newer.Narrative.String)

	// Check for patterns
	for _, p := range NarrativeMentionPatterns {
		if strings.Contains(narrative, p.Pattern) {
			// Found a pattern - this is a potential relationship
			confidence := 0.4 + p.ConfBoost
			if confidence > 1.0 {
				confidence = 1.0
			}

			return &RelationDetectionResult{
				SourceID:        newer.ID,
				TargetID:        older.ID,
				RelationType:    p.RelationType,
				Confidence:      confidence,
				DetectionSource: DetectionSourceNarrativeMention,
				Reason:          "narrative contains '" + strings.TrimSpace(p.Pattern) + "' language",
			}
		}
	}

	return nil
}

// DetectRelationsWithExisting checks a new observation against existing ones and returns detected relations.
// This is the main entry point for relation detection.
func DetectRelationsWithExisting(newer *Observation, existing []*Observation, minConfidence float64) []*RelationDetectionResult {
	var results []*RelationDetectionResult
	seen := make(map[int64]bool)

	for _, older := range existing {
		// Skip self
		if older.ID == newer.ID {
			continue
		}

		// Skip if already superseded
		if older.IsSuperseded {
			continue
		}

		// Only compare within same project (or both global)
		if newer.Project != older.Project && newer.Scope != ScopeGlobal && older.Scope != ScopeGlobal {
			continue
		}

		// Run all detection methods and keep highest confidence result per target
		var bestResult *RelationDetectionResult

		// 1. File overlap detection
		if result := DetectFileOverlapRelation(newer, older); result != nil && result.Confidence >= minConfidence {
			if bestResult == nil || result.Confidence > bestResult.Confidence {
				bestResult = result
			}
		}

		// 2. Concept overlap detection
		if result := DetectConceptOverlapRelation(newer, older); result != nil && result.Confidence >= minConfidence {
			if bestResult == nil || result.Confidence > bestResult.Confidence {
				bestResult = result
			}
		}

		// 3. Type progression detection
		if result := DetectTypeProgressionRelation(newer, older); result != nil && result.Confidence >= minConfidence {
			if bestResult == nil || result.Confidence > bestResult.Confidence {
				bestResult = result
			}
		}

		// 4. Temporal proximity detection
		if result := DetectTemporalProximityRelation(newer, older); result != nil && result.Confidence >= minConfidence {
			// Only use temporal proximity if no better detection found
			if bestResult == nil {
				bestResult = result
			}
		}

		// 5. Narrative mention detection (can upgrade relation type)
		if result := DetectNarrativeMentionRelation(newer, older); result != nil && result.Confidence >= minConfidence {
			if bestResult == nil || result.Confidence > bestResult.Confidence {
				bestResult = result
			}
		}

		// Add best result if found and not already seen
		if bestResult != nil && !seen[older.ID] {
			results = append(results, bestResult)
			seen[older.ID] = true
		}
	}

	return results
}

// truncateList truncates a list to maxLen items.
func truncateList(items []string, maxLen int) []string {
	if len(items) <= maxLen {
		return items
	}
	result := items[:maxLen]
	return append(result, "...")
}

// RelationWithDetails contains a relation with its observation details.
type RelationWithDetails struct {
	Relation    *ObservationRelation `json:"relation"`
	SourceTitle string               `json:"source_title"`
	TargetTitle string               `json:"target_title"`
	SourceType  ObservationType      `json:"source_type"`
	TargetType  ObservationType      `json:"target_type"`
}

// RelationGraph represents a graph of related observations.
type RelationGraph struct {
	Relations []*RelationWithDetails `json:"relations"`
	CenterID  int64                  `json:"center_id"`
}
