package http

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/nextlevelbuilder/goclaw/internal/i18n"
	"github.com/nextlevelbuilder/goclaw/internal/store"
)

// ModelInfo is a normalized model entry returned by the list-models endpoint.
type ModelInfo struct {
	ID   string `json:"id"`
	Name string `json:"name,omitempty"`
}

// handleListProviderModels proxies to the upstream provider API to list
// available models for the given provider.
//
//	GET /v1/providers/{id}/models
func (h *ProvidersHandler) handleListProviderModels(w http.ResponseWriter, r *http.Request) {
	locale := extractLocale(r)
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "provider")})
		return
	}

	p, err := h.store.GetProvider(r.Context(), id)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgNotFound, "provider", id.String())})
		return
	}

	// Claude CLI doesn't need an API key — return hardcoded models
	if p.ProviderType == store.ProviderClaudeCLI {
		writeJSON(w, http.StatusOK, map[string]any{"models": claudeCLIModels()})
		return
	}

	// ACP agents don't need an API key — return hardcoded models
	if p.ProviderType == store.ProviderACP {
		writeJSON(w, http.StatusOK, map[string]any{"models": acpModels()})
		return
	}

	if p.APIKey == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "API key")})
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
	defer cancel()

	var models []ModelInfo

	switch p.ProviderType {
	case "anthropic_native":
		models, err = fetchAnthropicModels(ctx, p.APIKey, h.resolveAPIBase(p))
	case "gemini_native":
		models, err = fetchGeminiModels(ctx, p.APIKey)
	case "bailian":
		models = bailianModels()
	case "dashscope":
		models = dashScopeModels()
	case "minimax_native":
		models = minimaxModels()
	case "suno":
		models = sunoModels()
	default:
		// All other types use OpenAI-compatible /models endpoint
		apiBase := strings.TrimRight(h.resolveAPIBase(p), "/")
		if apiBase == "" {
			apiBase = "https://api.openai.com/v1"
		}
		models, err = fetchOpenAIModels(ctx, apiBase, p.APIKey)
	}

	if err != nil {
		slog.Warn("providers.models", "provider", p.Name, "error", err)
		// Return empty list instead of error — provider may not support /models
		writeJSON(w, http.StatusOK, map[string]any{"models": []ModelInfo{}})
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{"models": models})
}

// fetchAnthropicModels calls the Anthropic models API.
func fetchAnthropicModels(ctx context.Context, apiKey, apiBase string) ([]ModelInfo, error) {
	base := strings.TrimRight(apiBase, "/")
	if base == "" {
		base = "https://api.anthropic.com/v1"
	}
	req, err := http.NewRequestWithContext(ctx, "GET", base+"/models", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("x-api-key", apiKey)
	req.Header.Set("anthropic-version", "2023-06-01")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return nil, fmt.Errorf("anthropic API returned %d: %s", resp.StatusCode, string(body))
	}

	var result struct {
		Data []struct {
			ID          string `json:"id"`
			DisplayName string `json:"display_name"`
		} `json:"data"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode anthropic response: %w", err)
	}

	models := make([]ModelInfo, 0, len(result.Data))
	for _, m := range result.Data {
		models = append(models, ModelInfo{ID: m.ID, Name: m.DisplayName})
	}
	return models, nil
}

// fetchGeminiModels calls the Google Gemini models API.
// Gemini uses a different format: GET /v1beta/models?key=API_KEY
func fetchGeminiModels(ctx context.Context, apiKey string) ([]ModelInfo, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", "https://generativelanguage.googleapis.com/v1beta/models?key="+apiKey, nil)
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return nil, fmt.Errorf("gemini API returned %d: %s", resp.StatusCode, string(body))
	}

	var result struct {
		Models []struct {
			Name        string `json:"name"`        // e.g. "models/gemini-2.0-flash"
			DisplayName string `json:"displayName"` // e.g. "Gemini 2.0 Flash"
		} `json:"models"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode gemini response: %w", err)
	}

	models := make([]ModelInfo, 0, len(result.Models))
	for _, m := range result.Models {
		// Strip "models/" prefix to get the usable model ID
		id := strings.TrimPrefix(m.Name, "models/")
		models = append(models, ModelInfo{ID: id, Name: m.DisplayName})
	}
	return models, nil
}

// bailianModels returns a hardcoded list of models available on the
// Bailian Coding platform (coding-intl.dashscope.aliyuncs.com).
// The platform does not expose a /v1/models endpoint.
func bailianModels() []ModelInfo {
	return []ModelInfo{
		{ID: "qwen3.5-plus", Name: "Qwen 3.5 Plus"},
		{ID: "kimi-k2.5", Name: "Kimi K2.5"},
		{ID: "GLM-5", Name: "GLM-5"},
		{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
		{ID: "qwen3-max-2026-01-23", Name: "Qwen 3 Max (2026-01-23)"},
		{ID: "qwen3-coder-next", Name: "Qwen 3 Coder Next"},
		{ID: "qwen3-coder-plus", Name: "Qwen 3 Coder Plus"},
		{ID: "glm-4.7", Name: "GLM 4.7"},
	}
}

// minimaxModels returns a hardcoded list of MiniMax models.
// MiniMax does not expose a /v1/models endpoint.
func minimaxModels() []ModelInfo {
	return []ModelInfo{
		// Chat / text
		{ID: "MiniMax-Text-01", Name: "MiniMax Text 01"},
		{ID: "MiniMax-M1", Name: "MiniMax M1"},
		{ID: "MiniMax-M2.5", Name: "MiniMax M2.5"},
		// Image generation
		{ID: "image-01", Name: "Image 01"},
		// Video generation
		{ID: "MiniMax-Hailuo-2.3", Name: "Hailuo Video 2.3"},
		{ID: "MiniMax-Hailuo-2", Name: "Hailuo Video 2"},
		{ID: "T2V-01-Director", Name: "T2V-01 Director"},
		// Music generation
		{ID: "music-2.5+", Name: "Music 2.5+"},
		{ID: "music-2.5", Name: "Music 2.5"},
		// TTS
		{ID: "speech-02-hd", Name: "Speech 02 HD"},
		{ID: "speech-02-turbo", Name: "Speech 02 Turbo"},
	}
}

// dashScopeModels returns a hardcoded list of DashScope (Qwen) models.
// DashScope does not expose a standard /v1/models endpoint.
func dashScopeModels() []ModelInfo {
	return []ModelInfo{
		// Qwen3.5 series — Text Generation + Deep Thinking + Visual Understanding
		{ID: "qwen3.5-plus", Name: "Qwen 3.5 Plus"},
		{ID: "qwen3.5-turbo", Name: "Qwen 3.5 Turbo"},
		// Qwen3 hosted series — Text + Thinking
		{ID: "qwen3-max", Name: "Qwen 3 Max"},
		{ID: "qwen3-plus", Name: "Qwen 3 Plus"},
		{ID: "qwen3-turbo", Name: "Qwen 3 Turbo"},
		// Image generation
		{ID: "wan2.6-image", Name: "Wan 2.6 Image"},
		{ID: "wan2.1-image", Name: "Wan 2.1 Image"},
		// Video generation
		{ID: "wan2.6-video", Name: "Wan 2.6 Video"},
	}
}

// sunoModels returns a hardcoded list of Suno music generation models.
func sunoModels() []ModelInfo {
	return []ModelInfo{
		{ID: "v4.5", Name: "Suno V4.5"},
		{ID: "v4", Name: "Suno V4"},
		{ID: "v3.5", Name: "Suno V3.5"},
	}
}

// claudeCLIModels returns the model aliases accepted by the Claude CLI.
func claudeCLIModels() []ModelInfo {
	return []ModelInfo{
		{ID: "sonnet", Name: "Sonnet"},
		{ID: "opus", Name: "Opus"},
		{ID: "haiku", Name: "Haiku"},
	}
}

// acpModels returns the model aliases for ACP-compatible coding agents.
func acpModels() []ModelInfo {
	return []ModelInfo{
		{ID: "claude", Name: "Claude"},
		{ID: "codex", Name: "Codex"},
		{ID: "gemini", Name: "Gemini"},
	}
}

// fetchOpenAIModels calls an OpenAI-compatible /models endpoint.
func fetchOpenAIModels(ctx context.Context, apiBase, apiKey string) ([]ModelInfo, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", apiBase+"/models", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		return nil, fmt.Errorf("provider API returned %d: %s", resp.StatusCode, string(body))
	}

	var result struct {
		Data []struct {
			ID      string `json:"id"`
			OwnedBy string `json:"owned_by"`
		} `json:"data"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("failed to decode provider response: %w", err)
	}

	models := make([]ModelInfo, 0, len(result.Data))
	for _, m := range result.Data {
		models = append(models, ModelInfo{ID: m.ID, Name: m.ID})
	}
	return models, nil
}
