package config

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"strings"
	"fmt"
	"os"
	"sync"
	"time"
)

// PlatformConfig holds persistent configuration for one platform adapter.
type PlatformConfig struct {
	ID        string            `json:"id"`        // immutable unique ID, generated at creation
	Name      string            `json:"name"`      // config key, e.g. "qq-main", "qq-work"
	Platform  string            `json:"platform"`  // base platform type: "obv11", "telegram", etc.
	Enabled   bool              `json:"enabled"`
	Label     string            `json:"label"`
	Fields    map[string]string `json:"fields"`
	CreatedAt time.Time         `json:"created_at"`
	UpdatedAt time.Time         `json:"updated_at"`
}

// Store manages persistence of platform configs to a JSON file.
type Store struct {
	mu      sync.RWMutex
	path    string
	configs map[string]*PlatformConfig
}

// NewStore creates a Store, creating the config file if it doesn't exist.
func NewStore(path string) (*Store, error) {
	s := &Store{
		path:    path,
		configs: make(map[string]*PlatformConfig),
	}
	if err := s.load(); err != nil {
		return nil, err
	}
	return s, nil
}

func (s *Store) load() error {
	data, err := os.ReadFile(s.path)
	if err != nil {
		if os.IsNotExist(err) {
			// Initialize empty file.
			return s.save()
		}
		return fmt.Errorf("read config file: %w", err)
	}
	if len(data) == 0 {
		return nil
	}
	if err := json.Unmarshal(data, &s.configs); err != nil {
		return fmt.Errorf("parse config file: %w", err)
	}
	// Backward compat: old configs without platform field default to Name.
	// Also migrate: generate IDs, rename qq→obv11.
	needsSave := false
	for _, c := range s.configs {
		if c.Platform == "" {
			c.Platform = c.Name
			needsSave = true
		}
		if c.ID == "" {
			c.ID = generateConfigID()
			c.CreatedAt = time.Now()
			needsSave = true
		}
		if c.Platform == "qq" {
			c.Platform = "obv11"
			needsSave = true
		}
		if strings.Contains(c.Name, "qq") {
			c.Name = strings.ReplaceAll(c.Name, "qq", "obv11")
			c.Label = strings.ReplaceAll(c.Label, "QQ", "OBv11")
			needsSave = true
		}
	}
	if needsSave {
		s.save()
	}
	return nil
}

func (s *Store) save() error {
	data, err := json.MarshalIndent(s.configs, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal configs: %w", err)
	}
	tmpPath := s.path + ".tmp"
	if err := os.WriteFile(tmpPath, data, 0640); err != nil {
		return fmt.Errorf("write config file: %w", err)
	}
	return os.Rename(tmpPath, s.path)
}

// List returns all platform configs.
func (s *Store) List() []PlatformConfig {
	s.mu.RLock()
	defer s.mu.RUnlock()
	result := make([]PlatformConfig, 0, len(s.configs))
	for _, c := range s.configs {
		result = append(result, *c)
	}
	return result
}

// Get returns a single platform config.
func (s *Store) Get(name string) (*PlatformConfig, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	c, ok := s.configs[name]
	if !ok {
		return nil, fmt.Errorf("config not found: %s", name)
	}
	return c, nil
}

// Set upserts a platform config and persists.
func (s *Store) Set(cfg PlatformConfig) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if cfg.Fields == nil {
		cfg.Fields = make(map[string]string)
	}
	// Generate stable ID for new configs.
	if cfg.ID == "" {
		cfg.ID = generateConfigID()
		cfg.CreatedAt = time.Now()
	}
	cfg.UpdatedAt = time.Now()
	s.configs[cfg.Name] = &cfg
	return s.save()
}

func generateConfigID() string {
	b := make([]byte, 8)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// Delete removes a platform config and persists.
func (s *Store) Delete(name string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.configs[name]; !ok {
		return fmt.Errorf("config not found: %s", name)
	}
	delete(s.configs, name)
	return s.save()
}

// HasConfig checks if a config exists for the given platform.
func (s *Store) HasConfig(name string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	_, ok := s.configs[name]
	return ok
}
