package ai

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const (
	DefaultDeepSeekBaseURL = "https://api.deepseek.com"
	DefaultDeepSeekModel   = "deepseek-v4-pro"
)

type Config struct {
	APIKey  string
	BaseURL string
	Model   string
}

type LocalConfig struct {
	URL         string
	Model       string
	Temperature float64
	TopP        float64
	NumPredict  int
}

type deepSeekMessage struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

type deepSeekChatRequest struct {
	Model          string            `json:"model"`
	Messages       []deepSeekMessage `json:"messages"`
	Thinking       map[string]string `json:"thinking,omitempty"`
	ResponseFormat map[string]string `json:"response_format,omitempty"`
	Temperature    float64           `json:"temperature"`
	MaxTokens      int               `json:"max_tokens"`
}

type deepSeekChatResponse struct {
	Choices []struct {
		FinishReason string `json:"finish_reason"`
		Message      struct {
			Role             string `json:"role"`
			Content          string `json:"content"`
			ReasoningContent string `json:"reasoning_content"`
		} `json:"message"`
	} `json:"choices"`
	Error *struct {
		Message string `json:"message"`
		Type    string `json:"type"`
	} `json:"error,omitempty"`
}

type slugResponse struct {
	Slug         string   `json:"slug"`
	Alternatives []string `json:"alternatives"`
}

type localGenerateRequest struct {
	Model   string       `json:"model"`
	Stream  bool         `json:"stream"`
	Options localOptions `json:"options"`
	Prompt  string       `json:"prompt"`
}

type localOptions struct {
	Temperature float64 `json:"temperature"`
	TopP        float64 `json:"top_p"`
	NumPredict  int     `json:"num_predict"`
}

type localGenerateResponse struct {
	Response string `json:"response"`
	Error    string `json:"error,omitempty"`
}

func GenerateSlug(ctx context.Context, config Config, title string, summary string) (string, error) {
	apiKey := strings.TrimSpace(config.APIKey)
	if apiKey == "" {
		return "", errors.New("DeepSeek API key is empty")
	}

	baseURL := strings.TrimRight(strings.TrimSpace(config.BaseURL), "/")
	if baseURL == "" {
		baseURL = DefaultDeepSeekBaseURL
	}
	model := strings.TrimSpace(config.Model)
	if model == "" {
		model = DefaultDeepSeekModel
	}

	userPrompt := fmt.Sprintf("Title: %s\nSummary: %s", title, summary)
	payload := deepSeekChatRequest{
		Model: model,
		Messages: []deepSeekMessage{
			{
				Role: "system",
				Content: `You generate concise English URL slugs for blog posts.
Return JSON only.
Rules:
- lowercase English
- use hyphen separators
- only a-z, 0-9, and hyphen
- translate non-English titles by meaning into natural English
- do not use pinyin, transliteration, romanization, or pronunciation-based spellings
- no leading or trailing hyphen
- max 80 characters
- preserve important technical terms
- do not include explanations
Examples:
- title "喜欢你" -> {"slug":"like-you","alternatives":["i-like-you","loving-you"]}
- title "用 Go 和 Astro 构建个人博客" -> {"slug":"building-a-personal-blog-with-go-and-astro","alternatives":["go-astro-personal-blog"]}
JSON format: {"slug":"example-slug","alternatives":["another-slug"]}`,
			},
			{Role: "user", Content: userPrompt},
		},
		Thinking:       map[string]string{"type": "disabled"},
		ResponseFormat: map[string]string{"type": "json_object"},
		Temperature:    0.2,
		MaxTokens:      300,
	}

	body, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/chat/completions", bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return "", fmt.Errorf("DeepSeek API returned %s: %s", resp.Status, strings.TrimSpace(string(respBody)))
	}

	var chatResp deepSeekChatResponse
	if err := json.Unmarshal(respBody, &chatResp); err != nil {
		return "", err
	}
	if chatResp.Error != nil {
		return "", fmt.Errorf("DeepSeek API error: %s", chatResp.Error.Message)
	}
	if len(chatResp.Choices) == 0 {
		return "", errors.New("DeepSeek API returned no choices")
	}

	choice := chatResp.Choices[0]
	content := strings.TrimSpace(choice.Message.Content)
	if content == "" {
		return "", fmt.Errorf("DeepSeek returned empty content, finish_reason=%q", choice.FinishReason)
	}

	var slugResp slugResponse
	if err := json.Unmarshal([]byte(content), &slugResp); err != nil {
		return "", fmt.Errorf("failed to parse DeepSeek slug JSON: %w", err)
	}

	slug := sanitizeSlug(slugResp.Slug)
	if slug == "" {
		for _, alternative := range slugResp.Alternatives {
			slug = sanitizeSlug(alternative)
			if slug != "" {
				break
			}
		}
	}
	if slug == "" {
		return "", errors.New("DeepSeek returned an empty slug")
	}
	return slug, nil
}

func GenerateLocalSlug(ctx context.Context, config LocalConfig, title string, summary string) (string, error) {
	url := strings.TrimSpace(config.URL)
	if url == "" {
		return "", errors.New("local LLM URL is empty")
	}
	model := strings.TrimSpace(config.Model)
	if model == "" {
		return "", errors.New("local LLM model is empty")
	}
	temperature := config.Temperature
	if temperature == 0 {
		temperature = 0.1
	}
	topP := config.TopP
	if topP == 0 {
		topP = 0.8
	}
	numPredict := config.NumPredict
	if numPredict == 0 {
		numPredict = 32
	}

	prompt := fmt.Sprintf(`Convert the following Chinese blog title into a concise English URL slug.
Output only the slug. Lowercase only. Use hyphens. Max 8 words.
Title: %s`, title)
	if strings.TrimSpace(summary) != "" {
		prompt += "\nSummary: " + summary
	}

	payload := localGenerateRequest{
		Model:  model,
		Stream: false,
		Options: localOptions{
			Temperature: temperature,
			TopP:        topP,
			NumPredict:  numPredict,
		},
		Prompt: prompt,
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return "", fmt.Errorf("local LLM returned %s: %s", resp.Status, strings.TrimSpace(string(respBody)))
	}

	var generated localGenerateResponse
	if err := json.Unmarshal(respBody, &generated); err != nil {
		return "", err
	}
	if generated.Error != "" {
		return "", fmt.Errorf("local LLM error: %s", generated.Error)
	}
	slug := sanitizeSlug(generated.Response)
	if slug == "" {
		return "", errors.New("local LLM returned an empty slug")
	}
	return slug, nil
}

func sanitizeSlug(slug string) string {
	slug = strings.ToLower(strings.TrimSpace(slug))
	re := regexp.MustCompile(`[^a-z0-9]+`)
	slug = re.ReplaceAllString(slug, "-")
	slug = strings.Trim(slug, "-")
	for strings.Contains(slug, "--") {
		slug = strings.ReplaceAll(slug, "--", "-")
	}
	if len(slug) > 80 {
		slug = strings.Trim(slug[:80], "-")
	}
	return slug
}
