package admin

import (
	"context"
	"database/sql"
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"go.uber.org/zap"

	"github.com/veylant/ia-gateway/internal/apierror"
	"github.com/veylant/ia-gateway/internal/crypto"
	"github.com/veylant/ia-gateway/internal/middleware"
	"github.com/veylant/ia-gateway/internal/provider"
	"github.com/veylant/ia-gateway/internal/provider/anthropic"
	"github.com/veylant/ia-gateway/internal/provider/azure"
	"github.com/veylant/ia-gateway/internal/provider/mistral"
	"github.com/veylant/ia-gateway/internal/provider/ollama"
	"github.com/veylant/ia-gateway/internal/provider/openai"
)

// ProviderAdapterRouter is the interface used by the admin handler to update
// adapters at runtime. Defined here to avoid import cycles with internal/router.
type ProviderAdapterRouter interface {
	ProviderRouter
	UpdateAdapter(name string, adapter provider.Adapter)
	RemoveAdapter(name string)
}

// ProviderConfig is the DB/JSON representation of a configured LLM provider.
type ProviderConfig struct {
	ID            string    `json:"id"`
	TenantID      string    `json:"tenant_id"`
	Provider      string    `json:"provider"`
	DisplayName   string    `json:"display_name"`
	APIKeyMasked  string    `json:"api_key,omitempty"`  // masked on read, plain on write
	BaseURL       string    `json:"base_url,omitempty"`
	ResourceName  string    `json:"resource_name,omitempty"`
	DeploymentID  string    `json:"deployment_id,omitempty"`
	APIVersion    string    `json:"api_version,omitempty"`
	TimeoutSec    int       `json:"timeout_sec"`
	MaxConns      int       `json:"max_conns"`
	ModelPatterns []string  `json:"model_patterns"`
	IsActive      bool      `json:"is_active"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

type upsertProviderRequest struct {
	Provider      string   `json:"provider"`
	DisplayName   string   `json:"display_name"`
	APIKey        string   `json:"api_key"`         // plaintext, encrypted before storage
	BaseURL       string   `json:"base_url"`
	ResourceName  string   `json:"resource_name"`
	DeploymentID  string   `json:"deployment_id"`
	APIVersion    string   `json:"api_version"`
	TimeoutSec    int      `json:"timeout_sec"`
	MaxConns      int      `json:"max_conns"`
	ModelPatterns []string `json:"model_patterns"`
	IsActive      *bool    `json:"is_active"`
}

// providerStore wraps *sql.DB for provider_configs CRUD.
type providerStore struct {
	db        *sql.DB
	encryptor *crypto.Encryptor
	logger    *zap.Logger
}

func newProviderStore(db *sql.DB, enc *crypto.Encryptor, logger *zap.Logger) *providerStore {
	return &providerStore{db: db, encryptor: enc, logger: logger}
}

func (s *providerStore) list(ctx context.Context, tenantID string) ([]ProviderConfig, error) {
	rows, err := s.db.QueryContext(ctx,
		`SELECT id, tenant_id, provider, display_name, api_key_enc, base_url,
		        resource_name, deployment_id, api_version, timeout_sec, max_conns,
		        model_patterns, is_active, created_at, updated_at
		 FROM provider_configs
		 WHERE tenant_id = $1
		 ORDER BY created_at ASC`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var configs []ProviderConfig
	for rows.Next() {
		var c ProviderConfig
		var apiKeyEnc string
		var patterns []string
		if err := rows.Scan(&c.ID, &c.TenantID, &c.Provider, &c.DisplayName,
			&apiKeyEnc, &c.BaseURL, &c.ResourceName, &c.DeploymentID, &c.APIVersion,
			&c.TimeoutSec, &c.MaxConns, (*pqStringArray)(&patterns),
			&c.IsActive, &c.CreatedAt, &c.UpdatedAt); err != nil {
			return nil, err
		}
		c.ModelPatterns = patterns
		c.APIKeyMasked = maskAPIKey(apiKeyEnc, s.encryptor)
		configs = append(configs, c)
	}
	return configs, rows.Err()
}

func (s *providerStore) get(ctx context.Context, id, tenantID string) (*ProviderConfig, string, error) {
	var c ProviderConfig
	var apiKeyEnc string
	var patterns []string
	err := s.db.QueryRowContext(ctx,
		`SELECT id, tenant_id, provider, display_name, api_key_enc, base_url,
		        resource_name, deployment_id, api_version, timeout_sec, max_conns,
		        model_patterns, is_active, created_at, updated_at
		 FROM provider_configs WHERE id = $1 AND tenant_id = $2`, id, tenantID,
	).Scan(&c.ID, &c.TenantID, &c.Provider, &c.DisplayName,
		&apiKeyEnc, &c.BaseURL, &c.ResourceName, &c.DeploymentID, &c.APIVersion,
		&c.TimeoutSec, &c.MaxConns, (*pqStringArray)(&patterns),
		&c.IsActive, &c.CreatedAt, &c.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, "", nil
	}
	c.ModelPatterns = patterns
	return &c, apiKeyEnc, err
}

func (s *providerStore) create(ctx context.Context, tenantID string, req upsertProviderRequest) (*ProviderConfig, error) {
	apiKeyEnc := encryptKey(req.APIKey, s.encryptor)
	isActive := true
	if req.IsActive != nil {
		isActive = *req.IsActive
	}
	timeoutSec := req.TimeoutSec
	if timeoutSec == 0 {
		timeoutSec = defaultTimeout(req.Provider)
	}
	maxConns := req.MaxConns
	if maxConns == 0 {
		maxConns = 100
	}
	apiVersion := req.APIVersion
	if apiVersion == "" {
		apiVersion = "2024-02-01"
	}

	var c ProviderConfig
	var storedPatterns []string
	err := s.db.QueryRowContext(ctx,
		`INSERT INTO provider_configs
		 (tenant_id, provider, display_name, api_key_enc, base_url, resource_name,
		  deployment_id, api_version, timeout_sec, max_conns, model_patterns, is_active)
		 VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::text[],$12)
		 ON CONFLICT (tenant_id, provider) DO UPDATE SET
		   display_name  = EXCLUDED.display_name,
		   api_key_enc   = CASE WHEN EXCLUDED.api_key_enc = '' THEN provider_configs.api_key_enc ELSE EXCLUDED.api_key_enc END,
		   base_url      = EXCLUDED.base_url,
		   resource_name = EXCLUDED.resource_name,
		   deployment_id = EXCLUDED.deployment_id,
		   api_version   = EXCLUDED.api_version,
		   timeout_sec   = EXCLUDED.timeout_sec,
		   max_conns     = EXCLUDED.max_conns,
		   model_patterns= EXCLUDED.model_patterns,
		   is_active     = EXCLUDED.is_active,
		   updated_at    = NOW()
		 RETURNING id, tenant_id, provider, display_name, api_key_enc, base_url,
		           resource_name, deployment_id, api_version, timeout_sec, max_conns,
		           model_patterns, is_active, created_at, updated_at`,
		tenantID, req.Provider, displayName(req), apiKeyEnc, req.BaseURL, req.ResourceName,
		req.DeploymentID, apiVersion, timeoutSec, maxConns,
		modelPatternsLiteral(req.ModelPatterns), isActive,
	).Scan(&c.ID, &c.TenantID, &c.Provider, &c.DisplayName,
		&apiKeyEnc, &c.BaseURL, &c.ResourceName, &c.DeploymentID, &c.APIVersion,
		&c.TimeoutSec, &c.MaxConns, (*pqStringArray)(&storedPatterns),
		&c.IsActive, &c.CreatedAt, &c.UpdatedAt)
	if err != nil {
		return nil, err
	}
	c.ModelPatterns = storedPatterns
	c.APIKeyMasked = maskAPIKey(apiKeyEnc, s.encryptor)
	return &c, nil
}

func (s *providerStore) delete(ctx context.Context, id, tenantID string) error {
	res, err := s.db.ExecContext(ctx,
		`DELETE FROM provider_configs WHERE id = $1 AND tenant_id = $2`, id, tenantID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// ─── HTTP handlers ────────────────────────────────────────────────────────────

func (h *Handler) listProviderConfigs(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	ps := newProviderStore(h.db, h.encryptor, h.logger)
	configs, err := ps.list(r.Context(), tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to list providers: "+err.Error()))
		return
	}
	if configs == nil {
		configs = []ProviderConfig{}
	}

	// Enrich with circuit breaker status if available
	type enriched struct {
		ProviderConfig
		State    string `json:"state,omitempty"`
		Failures int    `json:"failures,omitempty"`
	}
	statuses := map[string]string{}
	failures := map[string]int{}
	if h.router != nil {
		for _, s := range h.router.ProviderStatuses() {
			statuses[s.Provider] = s.State
			failures[s.Provider] = s.Failures
		}
	}
	result := make([]enriched, len(configs))
	for i, c := range configs {
		result[i] = enriched{
			ProviderConfig: c,
			State:          statuses[c.Provider],
			Failures:       failures[c.Provider],
		}
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"data": result})
}

func (h *Handler) createProviderConfig(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	var req upsertProviderRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	if !validProvider(req.Provider) {
		apierror.WriteError(w, apierror.NewBadRequestError("provider must be one of: openai, anthropic, azure, mistral, ollama"))
		return
	}

	ps := newProviderStore(h.db, h.encryptor, h.logger)
	cfg, err := ps.create(r.Context(), tenantID, req)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to save provider: "+err.Error()))
		return
	}

	// Rebuild and hot-reload the adapter
	h.reloadAdapter(r.Context(), cfg, req.APIKey)
	h.logger.Info("provider config saved", zap.String("provider", cfg.Provider), zap.String("tenant", tenantID))
	writeJSON(w, http.StatusCreated, cfg)
}

func (h *Handler) updateProviderConfig(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	id := chi.URLParam(r, "id")
	var req upsertProviderRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}

	ps := newProviderStore(h.db, h.encryptor, h.logger)
	// Fetch existing to get provider name if not supplied
	existing, existingKeyEnc, err := ps.get(r.Context(), id, tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	if existing == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "provider not found", HTTPStatus: http.StatusNotFound})
		return
	}
	if req.Provider == "" {
		req.Provider = existing.Provider
	}

	// If no new API key provided, keep the existing encrypted one by passing empty string
	// (the upsert handles this via the CASE statement)
	decryptedKey := req.APIKey
	if decryptedKey == "" && existingKeyEnc != "" {
		// Decrypt existing key to pass to adapter rebuild
		if h.encryptor != nil {
			if dec, decErr := h.encryptor.Decrypt(existingKeyEnc); decErr == nil {
				decryptedKey = dec
			}
		} else {
			decryptedKey = existingKeyEnc
		}
	}

	cfg, err := ps.create(r.Context(), tenantID, req) // upsert
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to update provider: "+err.Error()))
		return
	}

	h.reloadAdapter(r.Context(), cfg, decryptedKey)
	h.logger.Info("provider config updated", zap.String("provider", cfg.Provider), zap.String("id", id))
	writeJSON(w, http.StatusOK, cfg)
}

func (h *Handler) deleteProviderConfig(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	id := chi.URLParam(r, "id")

	ps := newProviderStore(h.db, h.encryptor, h.logger)
	existing, _, err := ps.get(r.Context(), id, tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	if existing == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "provider not found", HTTPStatus: http.StatusNotFound})
		return
	}

	if err := ps.delete(r.Context(), id, tenantID); err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to delete provider: "+err.Error()))
		return
	}

	// Remove adapter from router (config-based providers survive if they have an API key in env)
	if h.adapterRouter != nil {
		h.adapterRouter.RemoveAdapter(existing.Provider)
	}
	h.logger.Info("provider config deleted", zap.String("provider", existing.Provider))
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) testProviderConfig(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	id := chi.URLParam(r, "id")

	ps := newProviderStore(h.db, h.encryptor, h.logger)
	existing, apiKeyEnc, err := ps.get(r.Context(), id, tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	if existing == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "provider not found", HTTPStatus: http.StatusNotFound})
		return
	}

	apiKey := ""
	if h.encryptor != nil && apiKeyEnc != "" {
		if dec, decErr := h.encryptor.Decrypt(apiKeyEnc); decErr == nil {
			apiKey = dec
		}
	} else {
		apiKey = apiKeyEnc
	}

	adapter := buildAdapter(existing, apiKey)
	if adapter == nil {
		apierror.WriteError(w, apierror.NewBadRequestError("cannot build adapter for provider "+existing.Provider))
		return
	}

	if err := adapter.HealthCheck(r.Context()); err != nil {
		writeJSON(w, http.StatusOK, map[string]interface{}{
			"healthy": false,
			"error":   err.Error(),
		})
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"healthy": true})
}

// ─── Adapter helpers ──────────────────────────────────────────────────────────

// reloadAdapter rebuilds the provider adapter from cfg and registers it on the router.
func (h *Handler) reloadAdapter(_ context.Context, cfg *ProviderConfig, apiKey string) {
	if h.adapterRouter == nil {
		return
	}
	adapter := buildAdapter(cfg, apiKey)
	if adapter != nil {
		h.adapterRouter.UpdateAdapter(cfg.Provider, adapter)
	}
}

// BuildProviderAdapter constructs the correct provider.Adapter from a ProviderConfig.
// Exported so main.go can call it when loading provider configs from DB at startup.
func BuildProviderAdapter(cfg *ProviderConfig, apiKey string) provider.Adapter {
	return buildAdapter(cfg, apiKey)
}

// buildAdapter constructs the correct provider.Adapter from a ProviderConfig.
func buildAdapter(cfg *ProviderConfig, apiKey string) provider.Adapter {
	switch cfg.Provider {
	case "openai":
		return openai.New(openai.Config{
			APIKey:         apiKey,
			BaseURL:        orDefault(cfg.BaseURL, "https://api.openai.com/v1"),
			TimeoutSeconds: orDefaultInt(cfg.TimeoutSec, 30),
			MaxConns:       orDefaultInt(cfg.MaxConns, 100),
		})
	case "anthropic":
		return anthropic.New(anthropic.Config{
			APIKey:         apiKey,
			BaseURL:        orDefault(cfg.BaseURL, "https://api.anthropic.com/v1"),
			Version:        "2023-06-01",
			TimeoutSeconds: orDefaultInt(cfg.TimeoutSec, 30),
			MaxConns:       orDefaultInt(cfg.MaxConns, 100),
		})
	case "azure":
		return azure.New(azure.Config{
			APIKey:         apiKey,
			ResourceName:   cfg.ResourceName,
			DeploymentID:   cfg.DeploymentID,
			APIVersion:     orDefault(cfg.APIVersion, "2024-02-01"),
			TimeoutSeconds: orDefaultInt(cfg.TimeoutSec, 30),
			MaxConns:       orDefaultInt(cfg.MaxConns, 100),
		})
	case "mistral":
		return mistral.New(mistral.Config{
			APIKey:         apiKey,
			BaseURL:        orDefault(cfg.BaseURL, "https://api.mistral.ai/v1"),
			TimeoutSeconds: orDefaultInt(cfg.TimeoutSec, 30),
			MaxConns:       orDefaultInt(cfg.MaxConns, 100),
		})
	case "ollama":
		return ollama.New(ollama.Config{
			BaseURL:        orDefault(cfg.BaseURL, "http://localhost:11434/v1"),
			TimeoutSeconds: orDefaultInt(cfg.TimeoutSec, 120),
			MaxConns:       orDefaultInt(cfg.MaxConns, 10),
		})
	}
	return nil
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

func validProvider(p string) bool {
	switch p {
	case "openai", "anthropic", "azure", "mistral", "ollama":
		return true
	}
	return false
}

func defaultTimeout(provider string) int {
	if provider == "ollama" {
		return 120
	}
	return 30
}

func displayName(req upsertProviderRequest) string {
	if req.DisplayName != "" {
		return req.DisplayName
	}
	return strings.Title(req.Provider) //nolint:staticcheck
}

func encryptKey(key string, enc *crypto.Encryptor) string {
	if key == "" {
		return ""
	}
	if enc == nil {
		return key // dev mode: store plaintext
	}
	encrypted, err := enc.Encrypt(key)
	if err != nil {
		return key // fallback to plaintext on error
	}
	return encrypted
}

func maskAPIKey(apiKeyEnc string, enc *crypto.Encryptor) string {
	if apiKeyEnc == "" {
		return ""
	}
	// Decrypt to get the real key, then mask it
	plaintext := apiKeyEnc
	if enc != nil {
		if dec, err := enc.Decrypt(apiKeyEnc); err == nil {
			plaintext = dec
		}
	}
	if len(plaintext) <= 8 {
		return strings.Repeat("•", len(plaintext))
	}
	return plaintext[:8] + "••••••••"
}

func orDefault(s, def string) string {
	if s == "" {
		return def
	}
	return s
}

func orDefaultInt(v, def int) int {
	if v == 0 {
		return def
	}
	return v
}

// pqStringArray is a simple []string scanner for PostgreSQL TEXT[] columns.
type pqStringArray []string

func (a *pqStringArray) Scan(src interface{}) error {
	if src == nil {
		*a = []string{}
		return nil
	}
	// PostgreSQL returns TEXT[] as "{val1,val2,...}" string
	s, ok := src.(string)
	if !ok {
		if b, ok := src.([]byte); ok {
			s = string(b)
		} else {
			*a = []string{}
			return nil
		}
	}
	s = strings.TrimPrefix(s, "{")
	s = strings.TrimSuffix(s, "}")
	if s == "" {
		*a = []string{}
		return nil
	}
	parts := strings.Split(s, ",")
	result := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.Trim(p, `"`)
		if p != "" {
			result = append(result, p)
		}
	}
	*a = result
	return nil
}

// Value implements driver.Valuer for pqStringArray (for INSERT).
func (a pqStringArray) Value() (interface{}, error) {
	if len(a) == 0 {
		return "{}", nil
	}
	parts := make([]string, len(a))
	for i, v := range a {
		parts[i] = `"` + strings.ReplaceAll(v, `"`, `\"`) + `"`
	}
	return "{" + strings.Join(parts, ",") + "}", nil
}

// modelPatternsLiteral converts a []string to a PostgreSQL array literal string
// (e.g. {"a","b"}) to be used with $N::text[] in queries.
// This avoids driver.Valuer issues with the pgx stdlib adapter.
func modelPatternsLiteral(patterns []string) string {
	if len(patterns) == 0 {
		return "{}"
	}
	parts := make([]string, len(patterns))
	for i, v := range patterns {
		parts[i] = `"` + strings.ReplaceAll(v, `"`, `\"`) + `"`
	}
	return "{" + strings.Join(parts, ",") + "}"
}

// claimsFromRequest is a local helper (avoids import of middleware in this file).
func claimsFromRequest(r *http.Request) *middleware.UserClaims {
	claims, _ := middleware.ClaimsFromContext(r.Context())
	return claims
}
