package style8d

import (
	"encoding/json"
	"fmt"
	"math"
	"regexp"
	"strings"
	"time"
)

type Post struct {
	Text       string `json:"text"`
	Permalink  string `json:"permalink,omitempty"`
	LikeCount  int    `json:"like_count,omitempty"`
	ReplyCount int    `json:"reply_count,omitempty"`
}

type Dimension struct {
	Summary  string   `json:"summary"`
	Evidence []string `json:"evidence"`
}

type PersonaDraft struct {
	Identity string `json:"identity"`
	Tone     string `json:"tone"`
	Audience string `json:"audience"`
	Hooks    string `json:"hooks"`
	Examples string `json:"examples"`
	Avoid    string `json:"avoid"`
}

type LLMOutput struct {
	D1Tone        Dimension    `json:"d1Tone"`
	D2Structure   Dimension    `json:"d2Structure"`
	D3Interaction Dimension    `json:"d3Interaction"`
	D4Topics      Dimension    `json:"d4Topics"`
	D5Rhythm      Dimension    `json:"d5Rhythm"`
	D6Visual      Dimension    `json:"d6Visual"`
	D7Conversion  Dimension    `json:"d7Conversion"`
	D8Risk        Dimension    `json:"d8Risk"`
	PersonaDraft  PersonaDraft `json:"personaDraft"`
}

type Engagement struct {
	MeasuredPosts       int    `json:"measuredPosts"`
	MedianInteractions  int    `json:"medianInteractions"`
	AverageInteractions int    `json:"averageInteractions"`
	PostsAboveThreshold int    `json:"postsAboveThreshold"`
	Threshold           int    `json:"threshold"`
	Verdict             string `json:"verdict"`
}

type StoredProfile struct {
	Username     string               `json:"username"`
	AnalyzedAt   string               `json:"analyzedAt"`
	PostCount    int                  `json:"postCount"`
	Engagement   Engagement           `json:"engagement"`
	SamplePosts  []Post               `json:"samplePosts,omitempty"`
	Analysis     map[string]Dimension `json:"analysis"`
	PersonaDraft string               `json:"personaDraft"`
}

func BuildUserPrompt(username string, posts []Post) string {
	var b strings.Builder
	fmt.Fprintf(&b, "對標帳號：@%s\n近期貼文樣本：\n\n", strings.TrimPrefix(username, "@"))
	limit := len(posts)
	if limit > 12 {
		limit = 12
	}
	for i := 0; i < limit; i++ {
		post := posts[i]
		text := post.Text
		if len(text) > 500 {
			text = text[:500]
		}
		fmt.Fprintf(&b, "[%d] %d讚 %d回覆\n%s\n\n", i+1, post.LikeCount, post.ReplyCount, text)
	}
	return b.String()
}

func EvaluateEngagement(posts []Post, threshold int) Engagement {
	if threshold <= 0 {
		threshold = 10
	}
	values := make([]float64, 0, len(posts))
	for _, post := range posts {
		score := float64(post.LikeCount) + float64(post.ReplyCount)*2
		if score > 0 {
			values = append(values, score)
		}
	}
	median := 0.0
	if len(values) > 0 {
		sorted := append([]float64(nil), values...)
		for i := 0; i < len(sorted); i++ {
			for j := i + 1; j < len(sorted); j++ {
				if sorted[j] < sorted[i] {
					sorted[i], sorted[j] = sorted[j], sorted[i]
				}
			}
		}
		mid := len(sorted) / 2
		if len(sorted)%2 == 1 {
			median = sorted[mid]
		} else {
			median = (sorted[mid-1] + sorted[mid]) / 2
		}
	}
	avg := 0.0
	for _, v := range values {
		avg += v
	}
	if len(values) > 0 {
		avg /= float64(len(values))
	}
	above := 0
	for _, v := range values {
		if v >= float64(threshold) {
			above++
		}
	}
	verdict := "unknown"
	if len(values) >= 3 {
		if median >= float64(threshold) && above >= 3 {
			verdict = "strong"
		} else {
			verdict = "usable"
		}
	}
	return Engagement{
		MeasuredPosts:       len(values),
		MedianInteractions:  int(math.Round(median)),
		AverageInteractions: int(math.Round(avg)),
		PostsAboveThreshold: above,
		Threshold:           threshold,
		Verdict:             verdict,
	}
}

func SerializePersonaDraft(draft PersonaDraft) string {
	parts := []string{
		"【我是誰】\n" + strings.TrimSpace(draft.Identity),
		"【語氣】\n" + strings.TrimSpace(draft.Tone),
		"【對誰說】\n" + strings.TrimSpace(draft.Audience),
		"【開場習慣】\n" + strings.TrimSpace(draft.Hooks),
		"【代表句範例】\n" + strings.TrimSpace(draft.Examples),
		"【避免】\n" + strings.TrimSpace(draft.Avoid),
	}
	return strings.Join(parts, "\n\n")
}

func ParseLLMOutput(raw string) (LLMOutput, error) {
	payload, err := extractJSONObject(raw)
	if err != nil {
		return LLMOutput{}, err
	}
	var root map[string]json.RawMessage
	if err := json.Unmarshal(payload, &root); err != nil {
		return LLMOutput{}, fmt.Errorf("parse style8d json: %w", err)
	}
	for _, key := range []string{"analysis", "style8d", "style8D", "result", "data", "output"} {
		if nested, ok := root[key]; ok {
			var merged map[string]json.RawMessage
			if err := json.Unmarshal(nested, &merged); err == nil {
				for k, v := range merged {
					if _, exists := root[k]; !exists {
						root[k] = v
					}
				}
			}
		}
	}
	out := LLMOutput{
		D1Tone:        parseDimension(root, "d1Tone", "d1", "D1", "tone"),
		D2Structure:   parseDimension(root, "d2Structure", "d2", "D2", "structure"),
		D3Interaction: parseDimension(root, "d3Interaction", "d3", "D3", "interaction"),
		D4Topics:      parseDimension(root, "d4Topics", "d4", "D4", "topics"),
		D5Rhythm:      parseDimension(root, "d5Rhythm", "d5", "D5", "rhythm"),
		D6Visual:      parseDimension(root, "d6Visual", "d6", "D6", "visual"),
		D7Conversion:  parseDimension(root, "d7Conversion", "d7", "D7", "conversion"),
		D8Risk:        parseDimension(root, "d8Risk", "d8", "D8", "risk"),
		PersonaDraft:  parsePersonaDraft(root),
	}
	if err := validateOutput(out); err != nil {
		return LLMOutput{}, err
	}
	return out, nil
}

func buildSamplePosts(posts []Post) []Post {
	limit := len(posts)
	if limit > 12 {
		limit = 12
	}
	out := make([]Post, 0, limit)
	for i := 0; i < limit; i++ {
		post := posts[i]
		text := strings.TrimSpace(post.Text)
		if len(text) > 500 {
			text = text[:500]
		}
		out = append(out, Post{
			Text:       text,
			Permalink:  strings.TrimSpace(post.Permalink),
			LikeCount:  post.LikeCount,
			ReplyCount: post.ReplyCount,
		})
	}
	return out
}

func BuildStoredProfile(username string, posts []Post, out LLMOutput) StoredProfile {
	return StoredProfile{
		Username:    strings.TrimPrefix(strings.TrimSpace(username), "@"),
		AnalyzedAt:  time.Now().UTC().Format(time.RFC3339),
		PostCount:   len(posts),
		Engagement:  EvaluateEngagement(posts, 10),
		SamplePosts: buildSamplePosts(posts),
		Analysis: map[string]Dimension{
			"d1Tone":        trimDimension(out.D1Tone),
			"d2Structure":   trimDimension(out.D2Structure),
			"d3Interaction": trimDimension(out.D3Interaction),
			"d4Topics":      trimDimension(out.D4Topics),
			"d5Rhythm":      trimDimension(out.D5Rhythm),
			"d6Visual":      trimDimension(out.D6Visual),
			"d7Conversion":  trimDimension(out.D7Conversion),
			"d8Risk":        trimDimension(out.D8Risk),
		},
		PersonaDraft: SerializePersonaDraft(out.PersonaDraft),
	}
}

func (p StoredProfile) JSON() (string, error) {
	raw, err := json.Marshal(p)
	if err != nil {
		return "", err
	}
	return string(raw), nil
}

func trimDimension(d Dimension) Dimension {
	d.Summary = strings.TrimSpace(d.Summary)
	if len(d.Evidence) > 4 {
		d.Evidence = d.Evidence[:4]
	}
	out := make([]string, 0, len(d.Evidence))
	for _, item := range d.Evidence {
		item = strings.TrimSpace(item)
		if item != "" {
			out = append(out, item)
		}
	}
	d.Evidence = out
	return d
}

func validateOutput(out LLMOutput) error {
	missing := []string{}
	for name, dim := range map[string]Dimension{
		"D1": out.D1Tone, "D2": out.D2Structure, "D3": out.D3Interaction, "D4": out.D4Topics,
		"D5": out.D5Rhythm, "D6": out.D6Visual, "D7": out.D7Conversion, "D8": out.D8Risk,
	} {
		if strings.TrimSpace(dim.Summary) == "" {
			missing = append(missing, name)
		}
	}
	if len(missing) > 0 {
		return fmt.Errorf("LLM 回傳缺少維度摘要：%s", strings.Join(missing, ", "))
	}
	if strings.TrimSpace(out.PersonaDraft.Identity) == "" && strings.TrimSpace(out.PersonaDraft.Tone) == "" {
		return fmt.Errorf("LLM 回傳缺少 personaDraft")
	}
	return nil
}

var codeFenceRE = regexp.MustCompile("(?s)^```(?:json)?\\s*(.*?)\\s*```$")

func extractJSONObject(raw string) ([]byte, error) {
	text := strings.TrimSpace(raw)
	if text == "" {
		return nil, fmt.Errorf("empty LLM response")
	}
	if m := codeFenceRE.FindStringSubmatch(text); len(m) == 2 {
		text = strings.TrimSpace(m[1])
	}
	start := strings.Index(text, "{")
	end := strings.LastIndex(text, "}")
	if start < 0 || end <= start {
		return nil, fmt.Errorf("LLM response does not contain JSON object")
	}
	return []byte(text[start : end+1]), nil
}

func parseDimension(root map[string]json.RawMessage, keys ...string) Dimension {
	for _, key := range keys {
		if raw, ok := root[key]; ok {
			if dim := decodeDimension(raw); strings.TrimSpace(dim.Summary) != "" {
				return dim
			}
		}
	}
	return Dimension{}
}

func decodeDimension(raw json.RawMessage) Dimension {
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil && strings.TrimSpace(asString) != "" {
		return Dimension{Summary: strings.TrimSpace(asString)}
	}
	var obj map[string]json.RawMessage
	if err := json.Unmarshal(raw, &obj); err != nil {
		return Dimension{}
	}
	summary := firstString(obj, "summary", "description", "analysis", "conclusion")
	evidence := firstStringSlice(obj, "evidence", "examples", "quotes", "proof")
	return Dimension{Summary: summary, Evidence: evidence}
}

func parsePersonaDraft(root map[string]json.RawMessage) PersonaDraft {
	for _, key := range []string{"personaDraft", "persona", "persona_draft", "voiceProfile"} {
		raw, ok := root[key]
		if !ok {
			continue
		}
		var asString string
		if err := json.Unmarshal(raw, &asString); err == nil && strings.TrimSpace(asString) != "" {
			return PersonaDraft{Identity: strings.TrimSpace(asString)}
		}
		var obj map[string]json.RawMessage
		if err := json.Unmarshal(raw, &obj); err != nil {
			continue
		}
		return PersonaDraft{
			Identity: firstString(obj, "identity", "role"),
			Tone:     firstString(obj, "tone", "voice"),
			Audience: firstString(obj, "audience", "targetAudience"),
			Hooks:    firstString(obj, "hooks", "openings"),
			Examples: firstString(obj, "examples", "sample"),
			Avoid:    firstString(obj, "avoid", "risks"),
		}
	}
	return PersonaDraft{}
}

func firstString(obj map[string]json.RawMessage, keys ...string) string {
	for _, key := range keys {
		raw, ok := obj[key]
		if !ok {
			continue
		}
		var value string
		if err := json.Unmarshal(raw, &value); err == nil {
			value = strings.TrimSpace(value)
			if value != "" {
				return value
			}
		}
	}
	return ""
}

func firstStringSlice(obj map[string]json.RawMessage, keys ...string) []string {
	for _, key := range keys {
		raw, ok := obj[key]
		if !ok {
			continue
		}
		var values []string
		if err := json.Unmarshal(raw, &values); err == nil {
			out := make([]string, 0, len(values))
			for _, item := range values {
				item = strings.TrimSpace(item)
				if item != "" {
					out = append(out, item)
				}
			}
			if len(out) > 0 {
				return out
			}
		}
		var single string
		if err := json.Unmarshal(raw, &single); err == nil {
			single = strings.TrimSpace(single)
			if single != "" {
				return []string{single}
			}
		}
	}
	return nil
}
