package config

import (
	"encoding/json"
	"fmt"
	"os"
	"sync"
)

// ProviderData mirrors the Gateway ProviderConfig JSON shape.
type ProviderData struct {
	Name         string            `json:"name"`
	BaseURL      string            `json:"base_url"`
	APIKey       string            `json:"api_key"`
	TimeoutSec   int               `json:"timeout_sec"`
	MaxRetries   int               `json:"max_retries"`
	APIVersion   string            `json:"api_version,omitempty"`
	ExtraHeaders map[string]string `json:"extra_headers,omitempty"`
}

// ModelData mirrors the Gateway ModelConfig JSON shape.
type ModelData struct {
	ID          string                 `json:"id"`
	Name        string                 `json:"name"`
	Provider    string                 `json:"provider"`
	Description string                 `json:"description"`
	Priority    int                    `json:"priority"`
	Tags        []string               `json:"tags"`
	Params      map[string]interface{} `json:"params"`
	Enabled     bool                   `json:"enabled"`
}

// RoutingData mirrors the Gateway RoutingRule JSON shape.
type RoutingData struct {
	Purpose       string   `json:"purpose"`
	FallbackChain []string `json:"fallback_chain"`
	Required      bool     `json:"required"`
}

// ModelsConfigData is the top-level config document (read-only mirror).
type ModelsConfigData struct {
	Version   string                    `json:"version"`
	Providers map[string]*ProviderData  `json:"providers"`
	Models    map[string]*ModelData     `json:"models"`
	Routing   map[string]*RoutingData   `json:"routing"`
}

// Loader provides read-only access to models.json.
type Loader struct {
	mu     sync.RWMutex
	path   string
	config *ModelsConfigData
}

// NewLoader reads models.json and returns a Loader. Returns nil config if file doesn't exist.
func NewLoader(path string) (*Loader, error) {
	l := &Loader{
		path: path,
		config: &ModelsConfigData{
			Version:   "1.0",
			Providers: make(map[string]*ProviderData),
			Models:    make(map[string]*ModelData),
			Routing:   make(map[string]*RoutingData),
		},
	}
	if err := l.load(); err != nil {
		return nil, err
	}
	return l, nil
}

func (l *Loader) load() error {
	data, err := os.ReadFile(l.path)
	if err != nil {
		if os.IsNotExist(err) {
			l.config = nil // Signal: use .env fallback.
			return nil
		}
		return fmt.Errorf("read model config: %w", err)
	}
	if len(data) == 0 {
		l.config = nil
		return nil
	}
	var cfg ModelsConfigData
	if err := json.Unmarshal(data, &cfg); err != nil {
		return fmt.Errorf("parse model config: %w", err)
	}
	if cfg.Providers == nil {
		cfg.Providers = make(map[string]*ProviderData)
	}
	if cfg.Models == nil {
		cfg.Models = make(map[string]*ModelData)
	}
	if cfg.Routing == nil {
		cfg.Routing = make(map[string]*RoutingData)
	}
	l.config = &cfg
	return nil
}

// HasConfig returns true if models.json exists and contains data.
func (l *Loader) HasConfig() bool {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.config != nil && (len(l.config.Providers) > 0 || len(l.config.Models) > 0)
}

// Reload re-reads the config file. Used for config updates without restart.
func (l *Loader) Reload() error {
	return l.load()
}

// GetConfig returns the current config (read-only).
func (l *Loader) GetConfig() *ModelsConfigData {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.config
}
