// Package config provides configuration management for claude-mnemonic.
package config

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

const (
	// DefaultWorkerPort is the default HTTP port for the worker service.
	DefaultWorkerPort = 37777

	// DefaultModel for SDK agent (use "haiku" for cost-efficient processing).
	// Claude Code CLI accepts aliases: haiku, sonnet, opus (always latest versions)
	DefaultModel = "haiku"
)

// DefaultObservationTypes are the observation types to include in context.
var DefaultObservationTypes = []string{
	"bugfix", "feature", "refactor", "change", "discovery", "decision",
}

// DefaultObservationConcepts are the concept tags to include in context.
var DefaultObservationConcepts = []string{
	"how-it-works", "why-it-exists", "what-changed",
	"problem-solution", "gotcha", "pattern", "trade-off",
}

// CriticalConcepts are concepts that indicate "must know" information.
// Observations with these concepts are prioritized in context injection.
var CriticalConcepts = []string{
	"gotcha", "pattern", "problem-solution", "trade-off",
}

// Config holds the application configuration.
// Field order optimized for memory alignment (fieldalignment).
type Config struct {
	ContextFullField          string   `json:"context_full_field"`
	DBPath                    string   `json:"db_path"`
	Model                     string   `json:"model"`
	ClaudeCodePath            string   `json:"claude_code_path"`
	EmbeddingModel            string   `json:"embedding_model"`
	VectorStorageStrategy     string   `json:"vector_storage_strategy"`
	ContextObsConcepts        []string `json:"context_obs_concepts"`
	ContextObsTypes           []string `json:"context_obs_types"`
	ContextFullCount          int      `json:"context_full_count"`
	GraphBranchFactor         int      `json:"graph_branch_factor"`
	GraphEdgeWeight           float64  `json:"graph_edge_weight"`
	ContextRelevanceThreshold float64  `json:"context_relevance_threshold"`
	RerankingCandidates       int      `json:"reranking_candidates"`
	WorkerPort                int      `json:"worker_port"`
	DeduplicationThreshold    float64  `json:"deduplication_threshold"`
	RerankingMinImprovement   float64  `json:"reranking_min_improvement"`
	ContextObservations       int      `json:"context_observations"`
	ContextMaxPromptResults   int      `json:"context_max_prompt_results"`
	ContextSessionCount       int      `json:"context_session_count"`
	MaxConns                  int      `json:"max_conns"`
	RerankingAlpha            float64  `json:"reranking_alpha"`
	GraphMaxHops              int      `json:"graph_max_hops"`
	RerankingResults          int      `json:"reranking_results"`
	GraphRebuildIntervalMin   int      `json:"graph_rebuild_interval_min"`
	HubThreshold              int      `json:"hub_threshold"`
	ObservationRetentionDays  int      `json:"observation_retention_days"`
	MaintenanceIntervalHours  int      `json:"maintenance_interval_hours"`
	ContextMaxTokensStartup   int      `json:"context_max_tokens_startup"`
	ContextMaxTokensPrompt    int      `json:"context_max_tokens_prompt"`
	ContextShowWorkTokens     bool     `json:"context_show_work_tokens"`
	ContextShowReadTokens     bool     `json:"context_show_read_tokens"`
	RerankingPureMode         bool     `json:"reranking_pure_mode"`
	GraphEnabled              bool     `json:"graph_enabled"`
	DeduplicationEnabled      bool     `json:"deduplication_enabled"`
	MaintenanceEnabled        bool     `json:"maintenance_enabled"`
	RerankingEnabled          bool     `json:"reranking_enabled"`
	ContextShowLastSummary    bool     `json:"context_show_last_summary"`
	CleanupStaleObservations  bool     `json:"cleanup_stale_observations"`
}

var (
	globalConfig *Config
	configOnce   sync.Once
	configMu     sync.RWMutex
)

// DataDir returns the data directory path (~/.claude-mnemonic).
func DataDir() string {
	home, _ := os.UserHomeDir()
	return filepath.Join(home, ".claude-mnemonic")
}

// DBPath returns the database file path.
func DBPath() string {
	return filepath.Join(DataDir(), "claude-mnemonic.db")
}

// SettingsPath returns the settings file path.
func SettingsPath() string {
	return filepath.Join(DataDir(), "settings.json")
}

// EnsureDataDir creates the data directory if it doesn't exist.
// Uses 0700 permissions (owner-only) for security.
func EnsureDataDir() error {
	return os.MkdirAll(DataDir(), 0700)
}

// EnsureSettings creates a default settings file if it doesn't exist.
func EnsureSettings() error {
	path := SettingsPath()

	// Check if file exists
	if _, err := os.Stat(path); err == nil {
		return nil // File exists
	}

	// Create default settings file with comments
	defaultSettings := `{
  "CLAUDE_MNEMONIC_WORKER_PORT": 37777,
  "CLAUDE_MNEMONIC_MODEL": "haiku",
  "CLAUDE_MNEMONIC_CONTEXT_OBSERVATIONS": 100,
  "CLAUDE_MNEMONIC_CONTEXT_FULL_COUNT": 25,
  "CLAUDE_MNEMONIC_CONTEXT_SESSION_COUNT": 10
}
`
	return os.WriteFile(path, []byte(defaultSettings), 0600)
}

// EnsureAll ensures all required directories and files exist.
func EnsureAll() error {
	if err := EnsureDataDir(); err != nil {
		return err
	}
	if err := EnsureSettings(); err != nil {
		return err
	}
	return nil
}

// DefaultEmbeddingModel is the default embedding model to use.
const DefaultEmbeddingModel = "bge-v1.5"

// Default returns a Config with default values.
func Default() *Config {
	return &Config{
		WorkerPort:                DefaultWorkerPort,
		DBPath:                    DBPath(),
		MaxConns:                  4,
		Model:                     DefaultModel,
		EmbeddingModel:            DefaultEmbeddingModel,
		RerankingEnabled:          true,  // Enable by default for improved relevance
		RerankingCandidates:       100,   // Retrieve top 100 candidates
		RerankingResults:          10,    // Return top 10 after reranking
		RerankingAlpha:            0.7,   // Favor cross-encoder score
		RerankingMinImprovement:   0,     // Always apply reranking
		GraphEnabled:              true,  // Enable graph-aware search by default
		GraphMaxHops:              2,     // Two-hop traversal
		GraphBranchFactor:         5,     // Expand top 5 neighbors per node
		GraphEdgeWeight:           0.3,   // Minimum edge weight to follow
		GraphRebuildIntervalMin:   60,    // Rebuild graph every 60 minutes
		VectorStorageStrategy:     "hub", // Hub storage strategy (LEANN-inspired)
		HubThreshold:              5,     // Require 5+ accesses to store embedding
		ContextObservations:       100,
		ContextFullCount:          25,
		ContextSessionCount:       10,
		ContextShowReadTokens:     true,
		ContextShowWorkTokens:     true,
		ContextFullField:          "narrative",
		ContextShowLastSummary:    true,
		ContextObsTypes:           DefaultObservationTypes,
		ContextObsConcepts:        DefaultObservationConcepts,
		ContextRelevanceThreshold: 0.3,   // Minimum 30% similarity to include
		ContextMaxPromptResults:   10,    // Cap at 10 results max (0 = no cap, threshold only)
		ContextMaxTokensStartup:   16000, // Max tokens for SessionStart context injection
		ContextMaxTokensPrompt:    8000,  // Max tokens for UserPromptSubmit context injection
		DeduplicationEnabled:      true,  // Enable write-time vector dedup
		DeduplicationThreshold:    0.9,   // Similarity threshold for merging (0.9 = very similar)
		MaintenanceEnabled:        true,  // Enable scheduled maintenance
		MaintenanceIntervalHours:  6,     // Run every 6 hours
		ObservationRetentionDays:  0,     // 0 = no age-based deletion (keep all)
		CleanupStaleObservations:  false, // Don't auto-cleanup stale observations
	}
}

// Load loads configuration from the settings file, merging with defaults.
func Load() (*Config, error) {
	cfg := Default()

	data, err := os.ReadFile(SettingsPath())
	if err != nil {
		if os.IsNotExist(err) {
			return cfg, nil
		}
		return nil, err
	}

	// Load settings into a map to preserve unknown fields
	var settings map[string]interface{}
	if err := json.Unmarshal(data, &settings); err != nil {
		return cfg, nil // Return defaults on parse error
	}

	// Map settings to config
	if v, ok := settings["CLAUDE_MNEMONIC_WORKER_PORT"].(float64); ok {
		cfg.WorkerPort = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_MODEL"].(string); ok {
		cfg.Model = v
	}
	if v, ok := settings["CLAUDE_CODE_PATH"].(string); ok {
		cfg.ClaudeCodePath = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_EMBEDDING_MODEL"].(string); ok && v != "" {
		cfg.EmbeddingModel = v
	}
	// Reranking settings
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_ENABLED"].(bool); ok {
		cfg.RerankingEnabled = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_CANDIDATES"].(float64); ok && v > 0 {
		cfg.RerankingCandidates = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_RESULTS"].(float64); ok && v > 0 {
		cfg.RerankingResults = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_ALPHA"].(float64); ok && v >= 0 && v <= 1 {
		cfg.RerankingAlpha = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_MIN_IMPROVEMENT"].(float64); ok && v >= 0 {
		cfg.RerankingMinImprovement = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_RERANKING_PURE_MODE"].(bool); ok {
		cfg.RerankingPureMode = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_OBSERVATIONS"].(float64); ok {
		cfg.ContextObservations = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_FULL_COUNT"].(float64); ok {
		cfg.ContextFullCount = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_SESSION_COUNT"].(float64); ok {
		cfg.ContextSessionCount = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_OBS_TYPES"].(string); ok && v != "" {
		cfg.ContextObsTypes = splitTrim(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_OBS_CONCEPTS"].(string); ok && v != "" {
		cfg.ContextObsConcepts = splitTrim(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_RELEVANCE_THRESHOLD"].(float64); ok && v >= 0 && v <= 1 {
		cfg.ContextRelevanceThreshold = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_MAX_PROMPT_RESULTS"].(float64); ok && v >= 0 {
		cfg.ContextMaxPromptResults = int(v)
	}
	// Graph settings
	if v, ok := settings["CLAUDE_MNEMONIC_GRAPH_ENABLED"].(bool); ok {
		cfg.GraphEnabled = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_GRAPH_MAX_HOPS"].(float64); ok && v > 0 {
		cfg.GraphMaxHops = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_GRAPH_BRANCH_FACTOR"].(float64); ok && v > 0 {
		cfg.GraphBranchFactor = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_GRAPH_EDGE_WEIGHT"].(float64); ok && v >= 0 && v <= 1 {
		cfg.GraphEdgeWeight = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_GRAPH_REBUILD_INTERVAL_MIN"].(float64); ok && v > 0 {
		cfg.GraphRebuildIntervalMin = int(v)
	}
	// Vector storage settings (LEANN Phase 2)
	if v, ok := settings["CLAUDE_MNEMONIC_VECTOR_STORAGE_STRATEGY"].(string); ok && v != "" {
		cfg.VectorStorageStrategy = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_HUB_THRESHOLD"].(float64); ok && v > 0 {
		cfg.HubThreshold = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_MAX_TOKENS_STARTUP"].(float64); ok && v > 0 {
		cfg.ContextMaxTokensStartup = int(v)
	}
	if v, ok := settings["CLAUDE_MNEMONIC_CONTEXT_MAX_TOKENS_PROMPT"].(float64); ok && v > 0 {
		cfg.ContextMaxTokensPrompt = int(v)
	}
	// Deduplication settings
	if v, ok := settings["CLAUDE_MNEMONIC_DEDUP_ENABLED"].(bool); ok {
		cfg.DeduplicationEnabled = v
	}
	if v, ok := settings["CLAUDE_MNEMONIC_DEDUP_THRESHOLD"].(float64); ok && v > 0 && v <= 1 {
		cfg.DeduplicationThreshold = v
	}

	// Also support env vars for dedup settings
	if v := os.Getenv("CLAUDE_MNEMONIC_DEDUP_ENABLED"); v != "" {
		cfg.DeduplicationEnabled = v == "true" || v == "1"
	}
	if v := os.Getenv("CLAUDE_MNEMONIC_DEDUP_THRESHOLD"); v != "" {
		if f, err := strconv.ParseFloat(v, 64); err == nil && f > 0 && f <= 1 {
			cfg.DeduplicationThreshold = f
		}
	}

	return cfg, nil
}

// splitTrim splits a comma-separated string and trims whitespace.
func splitTrim(s string) []string {
	parts := strings.Split(s, ",")
	result := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			result = append(result, p)
		}
	}
	return result
}

// Get returns the global configuration, loading it if necessary.
func Get() *Config {
	configOnce.Do(func() {
		var err error
		globalConfig, err = Load()
		if err != nil {
			globalConfig = Default()
		}
	})

	configMu.RLock()
	defer configMu.RUnlock()
	return globalConfig
}

// GetWorkerPort returns the worker port from environment or config.
func GetWorkerPort() int {
	if port := os.Getenv("CLAUDE_MNEMONIC_WORKER_PORT"); port != "" {
		var p int
		if err := json.Unmarshal([]byte(port), &p); err == nil && p > 0 {
			return p
		}
	}
	return Get().WorkerPort
}
