package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"
)

const groqAPIURL = "https://api.groq.com/openai/v1/chat/completions"

var (
	analysisSvcOnce sync.Once
	analysisSvc     *AnalysisService
)

// AnalysisService 공시·뉴스를 Groq LLM으로 분석하는 서비스
type AnalysisService struct {
	dartSvc    *DartService
	newsSvc    *NewsService
	cache      *CacheService
	groqAPIKey string // Groq API 키
	groqModel  string // 사용할 모델명
	httpClient *http.Client
}

// GetAnalysisService 싱글턴 반환
func GetAnalysisService(groqAPIKey, groqModel string) *AnalysisService {
	analysisSvcOnce.Do(func() {
		analysisSvc = &AnalysisService{
			dartSvc:    GetDartService(),
			newsSvc:    GetNewsService(),
			cache:      GetCacheService(),
			groqAPIKey: groqAPIKey,
			groqModel:  groqModel,
			httpClient: &http.Client{Timeout: 30 * time.Second},
		}
	})
	return analysisSvc
}

// Analyze 공시·뉴스를 Groq LLM으로 분석하여 sentiment, reason 반환
// sentiment: "호재" | "악재" | "중립" | "정보없음"
func (s *AnalysisService) Analyze(code, name string) (string, string) {

	kst, _ := time.LoadLocation("Asia/Seoul")
	now := time.Now().In(kst)
	today := now.Format("20060102")
	yesterday := now.AddDate(0, 0, -1).Format("20060102")

	// 캐시 확인 (10분 TTL)
	cacheKey := "analysis:" + code + today
	if cached, ok := s.cache.Get(cacheKey); ok {
		if pair, ok := cached.([2]string); ok {
			return pair[0], pair[1]
		}
	}

	// 공시 조회 (오늘/어제 필터)
	var disclosureTitles []string
	disclosures, err := s.dartSvc.GetDisclosures(code)
	if err != nil {
		log.Printf("분석서비스 공시 조회 실패 [%s]: %v", code, err)
	} else {
		for _, d := range disclosures {
			if d.RceptDt == today || d.RceptDt == yesterday {
				disclosureTitles = append(disclosureTitles, d.ReportNm)
			}
		}
	}

	// 뉴스 조회 (오늘/어제 필터)
	var newsTitles []string
	newsItems, err := s.newsSvc.GetNews(name)
	if err != nil {
		log.Printf("분석서비스 뉴스 조회 실패 [%s]: %v", name, err)
	} else {
		for _, item := range newsItems {
			if isRecentDate(item.PublishedAt, today, yesterday) {
				newsTitles = append(newsTitles, item.Title)
			}
		}
	}

	// 공시·뉴스 모두 없으면 API 호출 생략
	if len(disclosureTitles) == 0 && len(newsTitles) == 0 {
		s.cache.Set(cacheKey, [2]string{"정보없음", ""}, 10*time.Minute)
		return "정보없음", ""
	}

	sentiment, reason := s.callGroqAPI(name, disclosureTitles, newsTitles)
	s.cache.Set(cacheKey, [2]string{sentiment, reason}, 10*time.Minute)
	return sentiment, reason
}

// callGroqAPI Groq API를 호출하여 호재/악재/중립 분류
func (s *AnalysisService) callGroqAPI(name string, disclosures, news []string) (string, string) {
	disclosureText := "없음"
	if len(disclosures) > 0 {
		disclosureText = strings.Join(disclosures, "\n")
	}
	newsText := "없음"
	if len(news) > 0 {
		newsText = strings.Join(news, "\n")
	}

	prompt := fmt.Sprintf(
		`당신은 주식 투자 전문가입니다. 아래 [%s] 종목의 공시와 뉴스를 보고`+
			` 투자자 관점에서 호재/악재/중립 중 하나로 분류하고, 이유를 15자 이내로 답하세요.`+
			` 반드시 JSON만 출력: {"sentiment":"호재","reason":"수주 계약 체결"}`+"\n\n"+
			"[공시]\n%s\n\n[뉴스]\n%s",
		name, disclosureText, newsText,
	)

	return s.callGroq(prompt, func(text string) (string, string) {
		var result struct {
			Sentiment string `json:"sentiment"`
			Reason    string `json:"reason"`
		}
		if err := json.Unmarshal([]byte(text), &result); err != nil {
			log.Printf("Groq 감성 분석 JSON 파싱 실패: %v (text=%s)", err, text)
			return "중립", ""
		}
		switch result.Sentiment {
		case "호재", "악재", "중립":
			return result.Sentiment, result.Reason
		default:
			return "중립", ""
		}
	})
}

// PredictTargetPriceFromSignal 체결강도 급등 종목 시그널로 단기 목표가 추론
func (s *AnalysisService) PredictTargetPriceFromSignal(
	code, name string,
	currentPrice, high, low, open int64,
	changeRate, cntrStr, prevCntrStr float64,
	risingCount int,
	sentiment, sentimentReason string,
) (int64, string) {

	cacheKey := fmt.Sprintf("target:%s:%d", code, currentPrice)
	if cached, ok := s.cache.Get(cacheKey); ok {
		if pair, ok := cached.([2]interface{}); ok {
			return pair[0].(int64), pair[1].(string)
		}
	}

	prompt := fmt.Sprintf(
		`당신은 주식 단기 매매 전문가입니다. 체결강도가 연속 상승 중인 [%s](%s) 종목을 매수했을 때 단기(당일~2일) 수익 실현 매도 목표가를 추론하세요.
목표가는 반드시 현재가(%d원)보다 높아야 합니다.
현재가: %d원 / 시가: %d원 / 고가: %d원 / 저가: %d원
등락률: %.2f%% / 체결강도: %.2f(직전 %.2f, %d회 연속 상승)
공시·뉴스 분석: %s (%s)
반드시 JSON만 출력: {"targetPrice":12500,"reason":"체결강도 급등+호재 공시"}`,
		name, code,
		currentPrice,
		currentPrice, open, high, low,
		changeRate, cntrStr, prevCntrStr, risingCount,
		sentiment, sentimentReason,
	)

	price, reason := s.callGroq(prompt, func(text string) (string, string) {
		var result struct {
			TargetPrice int64  `json:"targetPrice"`
			Reason      string `json:"reason"`
		}
		if err := json.Unmarshal([]byte(text), &result); err != nil {
			log.Printf("목표가 추론 JSON 파싱 실패 [%s]: %v (text=%s)", code, err, text)
			return "", ""
		}
		if result.TargetPrice <= currentPrice {
			log.Printf("목표가 무효 [%s]: AI 반환값 %d원 ≤ 현재가 %d원", code, result.TargetPrice, currentPrice)
			return "", ""
		}
		return fmt.Sprintf("%d", result.TargetPrice), result.Reason
	})

	if price == "" {
		return 0, ""
	}

	var targetPrice int64
	fmt.Sscanf(price, "%d", &targetPrice)
	s.cache.Set(cacheKey, [2]interface{}{targetPrice, reason}, 5*time.Minute)
	return targetPrice, reason
}

// PredictNextDayTrend 체결강도·시세·감성 데이터를 바탕으로 익일 주가 추세를 예측
// trend: "상승" | "하락" | "횡보", confidence: "높음" | "보통" | "낮음"
func (s *AnalysisService) PredictNextDayTrend(
	code, name string,
	currentPrice, high, low, open int64,
	changeRate, cntrStr float64,
	risingCount int,
	sentiment, sentimentReason string,
) (trend, confidence, reason string) {

	kst, _ := time.LoadLocation("Asia/Seoul")
	today := time.Now().In(kst).Format("20060102")

	cacheKey := fmt.Sprintf("nextday:%s:%s", code, today)
	if cached, ok := s.cache.Get(cacheKey); ok {
		if arr, ok := cached.([3]string); ok {
			return arr[0], arr[1], arr[2]
		}
	}

	prompt := fmt.Sprintf(
		`당신은 한국 주식 단기 매매 전문가입니다. 아래 데이터를 종합하여 [%s](%s) 종목의 익일(다음 거래일) 주가 추세를 예측하세요.

현재가: %d원 / 시가: %d원 / 고가: %d원 / 저가: %d원
당일 등락률: %.2f%% / 체결강도: %.2f (%d회 연속 상승)
오늘 공시·뉴스 분석: %s (%s)

판단 기준:
- 체결강도 연속 상승·호재 공시·양봉 마감이면 "상승" 가능성
- 체결강도 100 미만·악재 공시·음봉 마감이면 "하락" 가능성
- 신호 혼재 혹은 근거 부족이면 "횡보"
- 신뢰도는 근거가 명확할수록 "높음", 애매하면 "낮음"

반드시 JSON만 출력: {"trend":"상승","confidence":"높음","reason":"체결강도 연속 급등+호재 공시"}`,
		name, code,
		currentPrice, open, high, low,
		changeRate, cntrStr, risingCount,
		sentiment, sentimentReason,
	)

	raw, reason := s.callGroq(prompt, func(text string) (string, string) {
		var result struct {
			Trend      string `json:"trend"`
			Confidence string `json:"confidence"`
			Reason     string `json:"reason"`
		}
		if err := json.Unmarshal([]byte(text), &result); err != nil {
			log.Printf("익일 추세 JSON 파싱 실패 [%s]: %v (text=%s)", code, err, text)
			return "", ""
		}
		switch result.Trend {
		case "상승", "하락", "횡보":
		default:
			result.Trend = "횡보"
		}
		// trend|confidence 를 첫 번째 반환값에 묶어서 전달
		return result.Trend + "|" + result.Confidence, result.Reason
	})

	if raw == "" {
		return "횡보", "", ""
	}

	parts := strings.SplitN(raw, "|", 2)
	trend = parts[0]
	if len(parts) > 1 {
		confidence = parts[1]
	}

	s.cache.Set(cacheKey, [3]string{trend, confidence, reason}, 30*time.Minute)
	return trend, confidence, reason
}

// callGroq Groq API 공통 호출 함수 (OpenAI 호환)
// parseFunc: 응답 텍스트를 파싱하여 결과 반환
func (s *AnalysisService) callGroq(prompt string, parseFunc func(string) (string, string)) (string, string) {
	reqBody, _ := json.Marshal(map[string]any{
		"model":  s.groqModel,
		"stream": false,
		"messages": []map[string]string{
			{"role": "user", "content": prompt},
		},
	})

	req, err := http.NewRequest("POST", groqAPIURL, bytes.NewReader(reqBody))
	if err != nil {
		log.Printf("Groq API 요청 생성 실패: %v", err)
		return "중립", ""
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+s.groqAPIKey)

	resp, err := s.httpClient.Do(req)
	if err != nil {
		log.Printf("Groq API 호출 실패: %v", err)
		return "중립", ""
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		log.Printf("Groq API 오류 [%d]: %s", resp.StatusCode, string(body))
		return "중립", ""
	}

	// OpenAI 호환 응답 파싱: choices[0].message.content
	var apiResp struct {
		Choices []struct {
			Message struct {
				Content string `json:"content"`
			} `json:"message"`
		} `json:"choices"`
	}
	if err := json.Unmarshal(body, &apiResp); err != nil {
		log.Printf("Groq API 응답 파싱 실패: %v", err)
		return "중립", ""
	}
	if len(apiResp.Choices) == 0 {
		log.Printf("Groq API 응답 choices 비어있음")
		return "중립", ""
	}

	text := strings.TrimSpace(apiResp.Choices[0].Message.Content)

	// JSON 블록 추출 (```json ... ``` 감싸진 경우 대비)
	if idx := strings.Index(text, "{"); idx >= 0 {
		if end := strings.LastIndex(text, "}"); end >= idx {
			text = text[idx : end+1]
		}
	}

	if text == "" {
		log.Printf("Groq API 응답 비어있음")
		return "중립", ""
	}

	return parseFunc(text)
}

// isRecentDate RFC1123Z 형식 날짜가 today/yesterday 포함 여부 확인
func isRecentDate(pubDate, today, yesterday string) bool {
	// RFC1123Z: "Mon, 02 Jan 2006 15:04:05 -0700"
	formats := []string{
		time.RFC1123Z,
		time.RFC1123,
	}
	for _, f := range formats {
		t, err := time.Parse(f, pubDate)
		if err == nil {
			kst, _ := time.LoadLocation("Asia/Seoul")
			d := t.In(kst).Format("20060102")
			return d == today || d == yesterday
		}
	}
	return false
}
