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

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"time"
)

// PatternType represents the category of detected pattern.
type PatternType string

const (
	// PatternTypeBug represents recurring bug patterns (e.g., "nil handling oversight").
	PatternTypeBug PatternType = "bug"
	// PatternTypeRefactor represents recurring refactoring approaches (e.g., "interface extraction").
	PatternTypeRefactor PatternType = "refactor"
	// PatternTypeArchitecture represents consistent architectural patterns.
	PatternTypeArchitecture PatternType = "architecture"
	// PatternTypeAntiPattern represents identified anti-patterns to avoid.
	PatternTypeAntiPattern PatternType = "anti-pattern"
	// PatternTypeBestPractice represents best practices that work consistently.
	PatternTypeBestPractice PatternType = "best-practice"
)

// PatternStatus represents the lifecycle status of a pattern.
type PatternStatus string

const (
	// PatternStatusActive means the pattern is actively being tracked and can be referenced.
	PatternStatusActive PatternStatus = "active"
	// PatternStatusDeprecated means the pattern has been superseded or is no longer relevant.
	PatternStatusDeprecated PatternStatus = "deprecated"
	// PatternStatusMerged means this pattern was merged into another pattern.
	PatternStatusMerged PatternStatus = "merged"
)

// Pattern represents a recurring pattern detected across observations.
// This enables Claude to reference historical insights: "I've encountered this pattern 12 times."
type Pattern struct {
	Status         PatternStatus   `db:"status" json:"status"`
	Name           string          `db:"name" json:"name"`
	Type           PatternType     `db:"type" json:"type"`
	CreatedAt      string          `db:"created_at" json:"created_at"`
	LastSeenAt     string          `db:"last_seen_at" json:"last_seen_at"`
	Signature      JSONStringArray `db:"signature" json:"signature"`
	Projects       JSONStringArray `db:"projects" json:"projects"`
	ObservationIDs JSONInt64Array  `db:"observation_ids" json:"observation_ids"`
	Recommendation sql.NullString  `db:"recommendation" json:"recommendation"`
	Description    sql.NullString  `db:"description" json:"description"`
	MergedIntoID   sql.NullInt64   `db:"merged_into_id" json:"merged_into_id,omitempty"`
	Frequency      int             `db:"frequency" json:"frequency"`
	Confidence     float64         `db:"confidence" json:"confidence"`
	ID             int64           `db:"id" json:"id"`
	LastSeenEpoch  int64           `db:"last_seen_at_epoch" json:"last_seen_at_epoch"`
	CreatedAtEpoch int64           `db:"created_at_epoch" json:"created_at_epoch"`
}

// JSONInt64Array is a custom type for handling JSON int64 arrays in SQLite.
type JSONInt64Array []int64

// Scan implements sql.Scanner for JSONInt64Array.
func (j *JSONInt64Array) Scan(src interface{}) error {
	if src == nil {
		*j = nil
		return nil
	}

	var data []byte
	switch v := src.(type) {
	case string:
		data = []byte(v)
	case []byte:
		data = v
	default:
		return nil
	}

	if len(data) == 0 {
		*j = nil
		return nil
	}

	return json.Unmarshal(data, j)
}

// Value implements driver.Valuer for JSONInt64Array.
func (j JSONInt64Array) Value() (driver.Value, error) {
	if j == nil {
		return nil, nil
	}
	return json.Marshal(j)
}

// PatternJSON is a JSON-friendly representation of Pattern.
type PatternJSON struct {
	Status         PatternStatus `json:"status"`
	Name           string        `json:"name"`
	Type           PatternType   `json:"type"`
	Description    string        `json:"description,omitempty"`
	CreatedAt      string        `json:"created_at"`
	Recommendation string        `json:"recommendation,omitempty"`
	LastSeenAt     string        `json:"last_seen_at"`
	Signature      []string      `json:"signature,omitempty"`
	ObservationIDs []int64       `json:"observation_ids,omitempty"`
	Projects       []string      `json:"projects,omitempty"`
	MergedIntoID   int64         `json:"merged_into_id,omitempty"`
	Confidence     float64       `json:"confidence"`
	Frequency      int           `json:"frequency"`
	LastSeenEpoch  int64         `json:"last_seen_at_epoch"`
	ID             int64         `json:"id"`
	CreatedAtEpoch int64         `json:"created_at_epoch"`
}

// MarshalJSON implements json.Marshaler for Pattern.
func (p *Pattern) MarshalJSON() ([]byte, error) {
	j := PatternJSON{
		ID:             p.ID,
		Name:           p.Name,
		Type:           p.Type,
		Signature:      p.Signature,
		Frequency:      p.Frequency,
		Projects:       p.Projects,
		ObservationIDs: p.ObservationIDs,
		Status:         p.Status,
		Confidence:     p.Confidence,
		LastSeenAt:     p.LastSeenAt,
		LastSeenEpoch:  p.LastSeenEpoch,
		CreatedAt:      p.CreatedAt,
		CreatedAtEpoch: p.CreatedAtEpoch,
	}
	if p.Description.Valid {
		j.Description = p.Description.String
	}
	if p.Recommendation.Valid {
		j.Recommendation = p.Recommendation.String
	}
	if p.MergedIntoID.Valid {
		j.MergedIntoID = p.MergedIntoID.Int64
	}
	return json.Marshal(j)
}

// NewPattern creates a new pattern from detected data.
func NewPattern(name string, patternType PatternType, description string, signature []string, project string, observationID int64) *Pattern {
	now := time.Now()
	return &Pattern{
		Name:           name,
		Type:           patternType,
		Description:    sql.NullString{String: description, Valid: description != ""},
		Signature:      signature,
		Frequency:      1,
		Projects:       []string{project},
		ObservationIDs: []int64{observationID},
		Status:         PatternStatusActive,
		Confidence:     0.5, // Initial confidence
		LastSeenAt:     now.Format(time.RFC3339),
		LastSeenEpoch:  now.UnixMilli(),
		CreatedAt:      now.Format(time.RFC3339),
		CreatedAtEpoch: now.UnixMilli(),
	}
}

// AddOccurrence records a new occurrence of this pattern.
func (p *Pattern) AddOccurrence(project string, observationID int64) {
	p.Frequency++

	// Add project if not already tracked
	found := false
	for _, proj := range p.Projects {
		if proj == project {
			found = true
			break
		}
	}
	if !found {
		p.Projects = append(p.Projects, project)
	}

	// Add observation ID if not already tracked
	for _, id := range p.ObservationIDs {
		if id == observationID {
			return
		}
	}
	p.ObservationIDs = append(p.ObservationIDs, observationID)

	// Update confidence based on frequency and cross-project occurrence
	p.updateConfidence()

	// Update last seen timestamp
	now := time.Now()
	p.LastSeenAt = now.Format(time.RFC3339)
	p.LastSeenEpoch = now.UnixMilli()
}

// updateConfidence adjusts confidence based on frequency and cross-project validation.
func (p *Pattern) updateConfidence() {
	// Base confidence from frequency (logarithmic scaling)
	freqConfidence := 0.3 + (0.4 * (float64(min(p.Frequency, 10)) / 10.0))

	// Cross-project bonus: patterns seen across multiple projects are more reliable
	projectBonus := 0.0
	if len(p.Projects) >= 2 {
		projectBonus = 0.1
	}
	if len(p.Projects) >= 5 {
		projectBonus = 0.2
	}

	p.Confidence = min(1.0, freqConfidence+projectBonus)
}

// PatternMatch represents a match between an observation and a potential pattern.
type PatternMatch struct {
	MatchedOn     string  `json:"matched_on"`
	SuggestedName string  `json:"suggested_name,omitempty"`
	PatternID     int64   `json:"pattern_id"`
	Score         float64 `json:"score"`
	IsNew         bool    `json:"is_new"`
}

// PatternSignatureKeywords are common keywords used in pattern detection.
var PatternSignatureKeywords = map[PatternType][]string{
	PatternTypeBug: {
		"nil", "null", "undefined", "panic", "crash", "error handling",
		"race condition", "deadlock", "memory leak", "overflow",
		"off-by-one", "boundary", "timeout", "concurrency",
	},
	PatternTypeRefactor: {
		"extract", "inline", "rename", "move", "split", "merge",
		"interface", "abstraction", "decouple", "simplify",
		"consolidate", "modularize", "encapsulate",
	},
	PatternTypeArchitecture: {
		"layer", "service", "repository", "controller", "handler",
		"middleware", "dependency injection", "factory", "singleton",
		"observer", "strategy", "adapter", "facade", "builder",
	},
	PatternTypeAntiPattern: {
		"god class", "spaghetti", "copy paste", "magic number",
		"hardcoded", "circular dependency", "premature optimization",
		"over-engineering", "feature envy", "data clump",
	},
	PatternTypeBestPractice: {
		"test", "validation", "logging", "monitoring", "documentation",
		"error handling", "retry", "timeout", "circuit breaker",
		"graceful shutdown", "health check", "metrics",
	},
}

// DetectPatternType analyzes concepts and content to determine pattern type.
func DetectPatternType(concepts []string, title, narrative string) PatternType {
	// Check concepts first
	for _, concept := range concepts {
		switch concept {
		case "anti-pattern":
			return PatternTypeAntiPattern
		case "best-practice":
			return PatternTypeBestPractice
		case "architecture":
			return PatternTypeArchitecture
		case "refactor":
			return PatternTypeRefactor
		}
	}

	// Check for bug-related patterns in content
	content := title + " " + narrative
	for _, keyword := range PatternSignatureKeywords[PatternTypeBug] {
		if containsIgnoreCase(content, keyword) {
			return PatternTypeBug
		}
	}

	// Default to refactor for other patterns
	return PatternTypeRefactor
}

// containsIgnoreCase checks if text contains substr (case-insensitive).
func containsIgnoreCase(text, substr string) bool {
	textLower := toLower(text)
	substrLower := toLower(substr)
	return contains(textLower, substrLower)
}

// Simple implementations to avoid strings package dependency in this file
func toLower(s string) string {
	b := make([]byte, len(s))
	for i := 0; i < len(s); i++ {
		c := s[i]
		if 'A' <= c && c <= 'Z' {
			c += 'a' - 'A'
		}
		b[i] = c
	}
	return string(b)
}

func contains(s, substr string) bool {
	if len(substr) > len(s) {
		return false
	}
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

// ExtractSignature creates a signature from observation content.
func ExtractSignature(concepts []string, title, narrative string) []string {
	var signature []string

	// Add all concepts
	signature = append(signature, concepts...)

	// Extract key terms from title (simple word extraction)
	for _, word := range splitWords(title) {
		if len(word) > 3 && isSignificantWord(word) {
			signature = append(signature, toLower(word))
		}
	}

	return uniqueStrings(signature)
}

// splitWords is a simple word splitter.
func splitWords(s string) []string {
	var words []string
	word := ""
	for _, r := range s {
		if r == ' ' || r == '-' || r == '_' || r == '.' || r == ',' {
			if word != "" {
				words = append(words, word)
				word = ""
			}
		} else {
			word += string(r)
		}
	}
	if word != "" {
		words = append(words, word)
	}
	return words
}

// isSignificantWord filters out common stop words.
func isSignificantWord(word string) bool {
	stopWords := map[string]bool{
		"the": true, "and": true, "for": true, "with": true, "that": true,
		"this": true, "from": true, "have": true, "not": true, "are": true,
		"was": true, "but": true, "all": true, "can": true, "had": true,
		"were": true, "been": true, "will": true, "when": true, "what": true,
	}
	return !stopWords[toLower(word)]
}

// uniqueStrings returns a slice with duplicate strings removed.
func uniqueStrings(s []string) []string {
	seen := make(map[string]bool)
	var result []string
	for _, v := range s {
		if !seen[v] {
			seen[v] = true
			result = append(result, v)
		}
	}
	return result
}

// CalculateMatchScore computes similarity between two signatures.
func CalculateMatchScore(sig1, sig2 []string) float64 {
	if len(sig1) == 0 || len(sig2) == 0 {
		return 0.0
	}

	set1 := make(map[string]bool)
	for _, s := range sig1 {
		set1[toLower(s)] = true
	}

	matches := 0
	for _, s := range sig2 {
		if set1[toLower(s)] {
			matches++
		}
	}

	// Jaccard similarity
	unionSize := len(sig1) + len(sig2) - matches
	if unionSize == 0 {
		return 0.0
	}
	return float64(matches) / float64(unionSize)
}
