// Package config управляет JSON-конфигами приложения: config.json, trunks.json, users.json.
// Обеспечивает атомарную запись (tmp → rename) и thread-safe доступ через sync.RWMutex.
package config

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/pulse-lets-go/internal/models"
)

// ScoringWeights — веса компонентов скора.
type ScoringWeights struct {
	CallScore float64 `json:"call_score"`
	LoadScore float64 `json:"load_score"`
	IdleScore float64 `json:"idle_score"`
	FailScore float64 `json:"fail_score"`
}

// ScoringConfig — пороги и веса для scoring engine.
type ScoringConfig struct {
	IdleCPUMin            float64        `json:"idle_cpu_min"`
	CallFailureRateLethal float64        `json:"call_failure_rate_lethal"`
	Weights               ScoringWeights `json:"weights"`
	SmoothingFactor       float64        `json:"smoothing_factor"`        // 0 = без сглаживания, 0.3 = рекомендуемый EWMA
	LoadAvgMultiplier     float64        `json:"load_avg_multiplier"`     // множитель load_avg → score (стандартный 50)
}

// TLSConfig — настройки HTTPS.
type TLSConfig struct {
	Enabled  bool   `json:"enabled"`
	CertFile string `json:"cert_file"`
	KeyFile  string `json:"key_file"`
}

// RateLimitConfig — ограничения частоты запросов.
type RateLimitConfig struct {
	Enabled     bool `json:"enabled"`
	RoutePerSec int  `json:"route_per_sec"`
	APIPerSec   int  `json:"api_per_sec"`
}

// LogConfig — настройки логирования.
type LogConfig struct {
	Level  string `json:"level"`  // debug, info, warn, error
	Format string `json:"format"` // text, json
}

// ESLConfig — настройки подключения к FreeSWITCH ESL.
type ESLConfig struct {
	Host          string `json:"host"` // если пустой — ESL не запускается
	Port          int    `json:"port"` // порт ESL (по умолчанию 8021)
	Password      string `json:"password"`
	PasswordEnv   string `json:"password_env"`   // имя переменной окружения с паролем
	SofiaProfile  string `json:"sofia_profile"`  // профиль Sofia (external/internal)
	GatewayPrefix string `json:"gateway_prefix"` // префикс gateway (pulse)
}

// GetPassword возвращает ESL-пароль: из переменной окружения, если задан password_env, иначе из поля password.
func (e *ESLConfig) GetPassword() string {
	if e.PasswordEnv != "" {
		if val := os.Getenv(e.PasswordEnv); val != "" {
			return val
		}
	}
	return e.Password
}

// GetJWTSecret возвращает JWT-секрет: из переменной окружения, если задан jwt_secret_env, иначе из поля jwt_secret.
func (c *Config) GetJWTSecret() string {
	if c.JWTSecretEnv != "" {
		if val := os.Getenv(c.JWTSecretEnv); val != "" {
			return val
		}
	}
	return c.JWTSecret
}

// Config — корневая конфигурация приложения (config.json).
type Config struct {
	NatsURL           string          `json:"nats_url"`
	NatsUser          string          `json:"nats_user"`
	NatsPassword      string          `json:"nats_password"`
	ListenAddr        string          `json:"listen_addr"`
	JWTSecret         string          `json:"jwt_secret"`
	JWTSecretEnv      string          `json:"jwt_secret_env"` // имя переменной окружения с JWT-секретом
	MonitoringAPIKey  string          `json:"monitoring_api_key"`
	StaleThresholdSec int             `json:"stale_threshold_sec"`
	Scoring           ScoringConfig   `json:"scoring"`
	TLS               TLSConfig       `json:"tls"`
	RateLimit         RateLimitConfig `json:"rate_limit"`
	Log               LogConfig       `json:"log"`
	ESL               ESLConfig       `json:"esl"`
	BalanceMode       string          `json:"balance_mode"` // "best" | "weighted_random"
}

// Validate проверяет корректность всех полей конфига.
// Возвращает читаемое описание всех найденных ошибок.
func (c *Config) Validate() error {
	var errs []string

	if c.NatsURL == "" {
		errs = append(errs, "nats_url обязателен")
	}
	if c.ListenAddr == "" {
		errs = append(errs, "listen_addr обязателен")
	}
	if c.StaleThresholdSec <= 0 {
		errs = append(errs, "stale_threshold_sec должен быть > 0")
	}
	if c.JWTSecret == "" {
		errs = append(errs, "jwt_secret обязателен")
	}
	if c.MonitoringAPIKey == "" {
		errs = append(errs, "monitoring_api_key обязателен")
	}
	if c.Scoring.IdleCPUMin < 0 {
		errs = append(errs, "scoring.idle_cpu_min должен быть >= 0")
	}
	if c.Scoring.CallFailureRateLethal < 0 || c.Scoring.CallFailureRateLethal > 100 {
		errs = append(errs, "scoring.call_failure_rate_lethal должен быть 0..100")
	}
	if c.Scoring.SmoothingFactor < 0 || c.Scoring.SmoothingFactor > 1 {
		errs = append(errs, "scoring.smoothing_factor должен быть 0..1")
	}
	if c.Scoring.LoadAvgMultiplier < 0 {
		errs = append(errs, "scoring.load_avg_multiplier должен быть >= 0")
	}

	w := &c.Scoring.Weights
	sum := w.CallScore + w.LoadScore + w.IdleScore + w.FailScore
	if math.Abs(sum-1.0) > 0.01 {
		errs = append(errs, fmt.Sprintf("scoring.weights сумма должна быть ≈1.0 (сейчас %.2f)", sum))
	}

	if c.TLS.Enabled {
		if c.TLS.CertFile == "" || c.TLS.KeyFile == "" {
			errs = append(errs, "tls.cert_file и tls.key_file обязательны при tls.enabled=true")
		}
	}

	if c.RateLimit.Enabled {
		if c.RateLimit.RoutePerSec <= 0 {
			errs = append(errs, "rate_limit.route_per_sec должен быть > 0")
		}
		if c.RateLimit.APIPerSec <= 0 {
			errs = append(errs, "rate_limit.api_per_sec должен быть > 0")
		}
	}

	switch c.Log.Level {
	case "debug", "info", "warn", "error", "":
	default:
		errs = append(errs, "log.level должен быть debug/info/warn/error")
	}
	switch c.Log.Format {
	case "text", "json", "":
	default:
		errs = append(errs, "log.format должен быть text/json")
	}

	// Balance mode validation
	switch c.BalanceMode {
	case "best", "weighted_random", "":
	default:
		errs = append(errs, "balance_mode должен быть best или weighted_random")
	}

	// ESL validation
	if c.ESL.Host != "" {
		if c.ESL.Port <= 0 || c.ESL.Port > 65535 {
			errs = append(errs, "esl.port должен быть 1-65535")
		}
		if c.ESL.SofiaProfile == "" {
			errs = append(errs, "esl.sofia_profile обязателен при esl.host")
		}
		if c.ESL.Password == "" && c.ESL.PasswordEnv == "" {
			errs = append(errs, "esl.password или esl.password_env обязателен при esl.host")
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("ошибки конфигурации:\n  - %s", strings.Join(errs, "\n  - "))
	}
	return nil
}

// Manager управляет чтением и атомарной записью JSON-конфигов.
type Manager struct {
	mu  sync.RWMutex
	dir string // путь к data/
}

// NewManager создаёт новый менеджер конфигов из директории dataDir.
// Если файлы отсутствуют — создаёт их с дефолтными значениями.
func NewManager(dataDir string) (*Manager, error) {
	// Убеждаемся, что директория существует
	if err := os.MkdirAll(dataDir, 0750); err != nil {
		return nil, fmt.Errorf("создание data/: %w", err)
	}

	m := &Manager{dir: dataDir}

	// Инициализируем конфиги при первом запуске
	if _, err := os.Stat(m.configPath()); os.IsNotExist(err) {
		if err := m.createDefaultConfig(); err != nil {
			return nil, fmt.Errorf("создание config.json: %w", err)
		}
	}
	if _, err := os.Stat(m.trunksPath()); os.IsNotExist(err) {
		if err := m.saveFile(m.trunksPath(), []models.Trunk{}); err != nil {
			return nil, fmt.Errorf("создание trunks.json: %w", err)
		}
	}
	if _, err := os.Stat(m.usersPath()); os.IsNotExist(err) {
		if err := m.createDefaultUsers(); err != nil {
			return nil, fmt.Errorf("создание users.json: %w", err)
		}
	}
	if _, err := os.Stat(m.routingRulesPath()); os.IsNotExist(err) {
		if err := m.saveFile(m.routingRulesPath(), []models.RouteRule{}); err != nil {
			return nil, fmt.Errorf("создание routing.json: %w", err)
		}
	}

	return m, nil
}

// ReadConfig читает config.json. Потокобезопасно.
func (m *Manager) ReadConfig() (*Config, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	data, err := os.ReadFile(m.configPath())
	if err != nil {
		return nil, fmt.Errorf("чтение config.json: %w", err)
	}

	var cfg Config
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("парсинг config.json: %w", err)
	}
	return &cfg, nil
}

// SaveConfig атомарно сохраняет config.json.
func (m *Manager) SaveConfig(cfg *Config) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	data, err := json.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return fmt.Errorf("сериализация config.json: %w", err)
	}
	return m.saveFile(m.configPath(), data)
}

// ReadTrunks читает trunks.json. Потокобезопасно.
func (m *Manager) ReadTrunks() ([]models.Trunk, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.readTrunksLocked()
}

// readTrunksLocked — внутреннее чтение без блокировки (вызывающий держит lock).
func (m *Manager) readTrunksLocked() ([]models.Trunk, error) {
	data, err := os.ReadFile(m.trunksPath())
	if err != nil {
		return nil, fmt.Errorf("чтение trunks.json: %w", err)
	}
	var trunks []models.Trunk
	if err := json.Unmarshal(data, &trunks); err != nil {
		return nil, fmt.Errorf("парсинг trunks.json: %w", err)
	}
	return trunks, nil
}

// SaveTrunks атомарно сохраняет trunks.json.
func (m *Manager) SaveTrunks(trunks []models.Trunk) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.saveTrunksLocked(trunks)
}

func (m *Manager) saveTrunksLocked(trunks []models.Trunk) error {
	data, err := json.MarshalIndent(trunks, "", "  ")
	if err != nil {
		return fmt.Errorf("сериализация trunks.json: %w", err)
	}
	return m.saveFile(m.trunksPath(), data)
}

// ReadUsers читает users.json. Потокобезопасно.
func (m *Manager) ReadUsers() ([]models.User, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.readUsersLocked()
}

func (m *Manager) readUsersLocked() ([]models.User, error) {
	data, err := os.ReadFile(m.usersPath())
	if err != nil {
		return nil, fmt.Errorf("чтение users.json: %w", err)
	}
	var users []models.User
	if err := json.Unmarshal(data, &users); err != nil {
		return nil, fmt.Errorf("парсинг users.json: %w", err)
	}
	return users, nil
}

// SaveUsers атомарно сохраняет users.json.
func (m *Manager) SaveUsers(users []models.User) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.saveUsersLocked(users)
}

func (m *Manager) saveUsersLocked(users []models.User) error {
	data, err := json.MarshalIndent(users, "", "  ")
	if err != nil {
		return fmt.Errorf("сериализация users.json: %w", err)
	}
	return m.saveFile(m.usersPath(), data)
}

// ReadRoutingRules читает routing.json. Потокобезопасно.
func (m *Manager) ReadRoutingRules() ([]models.RouteRule, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.readRoutingRulesLocked()
}

func (m *Manager) readRoutingRulesLocked() ([]models.RouteRule, error) {
	data, err := os.ReadFile(m.routingRulesPath())
	if err != nil {
		return nil, fmt.Errorf("чтение routing.json: %w", err)
	}
	var rules []models.RouteRule
	if err := json.Unmarshal(data, &rules); err != nil {
		return nil, fmt.Errorf("парсинг routing.json: %w", err)
	}
	return rules, nil
}

// SaveRoutingRules атомарно сохраняет routing.json.
func (m *Manager) SaveRoutingRules(rules []models.RouteRule) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.saveRoutingRulesLocked(rules)
}

func (m *Manager) saveRoutingRulesLocked(rules []models.RouteRule) error {
	data, err := json.MarshalIndent(rules, "", "  ")
	if err != nil {
		return fmt.Errorf("сериализация routing.json: %w", err)
	}
	return m.saveFile(m.routingRulesPath(), data)
}

// --- Приватные методы ---

func (m *Manager) configPath() string      { return filepath.Join(m.dir, "config.json") }
func (m *Manager) trunksPath() string      { return filepath.Join(m.dir, "trunks.json") }
func (m *Manager) usersPath() string       { return filepath.Join(m.dir, "users.json") }
func (m *Manager) routingRulesPath() string { return filepath.Join(m.dir, "routing.json") }

// saveFile атомарно сохраняет данные в файл: tmp → rename.
// Вызывающий держит write lock.
func (m *Manager) saveFile(path string, data interface{}) error {
	var raw []byte
	var err error

	switch v := data.(type) {
	case []byte:
		raw = v
	default:
		raw, err = json.MarshalIndent(data, "", "  ")
		if err != nil {
			return fmt.Errorf("сериализация: %w", err)
		}
	}

	tmpPath := path + ".tmp"
	if err := os.WriteFile(tmpPath, raw, 0640); err != nil {
		return fmt.Errorf("запись временного файла: %w", err)
	}

	if err := os.Rename(tmpPath, path); err != nil {
		os.Remove(tmpPath) // clean up
		return fmt.Errorf("атомарный rename: %w", err)
	}

	return nil
}

// createDefaultConfig генерирует config.json со значениями по умолчанию.
func (m *Manager) createDefaultConfig() error {
	jwtSecret, err := generateRandomHex(32)
	if err != nil {
		return err
	}
	apiKey, err := generateRandomHex(32)
	if err != nil {
		return err
	}

	cfg := Config{
		NatsURL:           "nats://localhost:4222",
		NatsUser:          "",
		NatsPassword:      "",
		ListenAddr:        ":8080",
		JWTSecret:         jwtSecret,
		MonitoringAPIKey:  apiKey,
		StaleThresholdSec: 10,
		Scoring: ScoringConfig{
			IdleCPUMin:            5.0,
			CallFailureRateLethal: 15.0,
			Weights: ScoringWeights{
				CallScore: 0.40,
				LoadScore: 0.30,
				IdleScore: 0.20,
				FailScore: 0.10,
			},
			SmoothingFactor:   0.3,
			LoadAvgMultiplier: 50.0,
		},
		TLS: TLSConfig{
			Enabled:  false,
			CertFile: "",
			KeyFile:  "",
		},
		RateLimit: RateLimitConfig{
			Enabled:     true,
			RoutePerSec: 5000,
			APIPerSec:   100,
		},
		Log: LogConfig{
			Level:  "info",
			Format: "text",
		},
		ESL: ESLConfig{
			Host:          "",
			Port:          8021,
			Password:      "",
			PasswordEnv:   "",
			SofiaProfile:  "external",
			GatewayPrefix: "pulse",
		},
		BalanceMode: "weighted_random",
	}

	return m.SaveConfig(&cfg)
}

// createDefaultUsers создаёт users.json с дефолтным админом (admin/admin).
// Пароль хранится в bcrypt. На первом запуске запрашивается смена.
func (m *Manager) createDefaultUsers() error {
	// Используем плейсхолдер-хэш "admin" — при первом логине админ обязан сменить пароль.
	// Хэш будет заменён на bcrypt при создании/обновлении через API.
	defaultTime := time.Now().UTC()
	users := []models.User{
		{
			ID:        "user-01",
			Username:  "admin",
			Role:      models.RoleAdmin,
			CreatedAt: defaultTime,
			UpdatedAt: defaultTime,
			// PasswordHash будет установлен при первом логине через API
		},
	}
	return m.saveUsersLocked(users)
}

// generateRandomHex генерирует случайную hex-строку заданной длины в байтах.
func generateRandomHex(bytes int) (string, error) {
	buf := make([]byte, bytes)
	if _, err := rand.Read(buf); err != nil {
		return "", fmt.Errorf("генерация случайных байт: %w", err)
	}
	return hex.EncodeToString(buf), nil
}
