package services

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"stocksearch/config"
	"stocksearch/models"
	"strings"
	"sync"
	"time"
	"unicode"
)

var (
	newsSvcOnce sync.Once
	newsSvc     *NewsService
)

// NewsService 네이버 뉴스 검색 서비스
type NewsService struct {
	httpClient   *http.Client
	cache        *CacheService
	clientID     string
	clientSecret string
}

// GetNewsService 싱글턴 반환
func GetNewsService() *NewsService {
	newsSvcOnce.Do(func() {
		newsSvc = &NewsService{
			httpClient:   &http.Client{Timeout: 5 * time.Second},
			cache:        GetCacheService(),
			clientID:     config.App.NaverClientID,
			clientSecret: config.App.NaverClientSecret,
		}
	})
	return newsSvc
}

// GetNews 종목명 기반 최근 뉴스 반환 (3분 캐시, 제목 중복 제거)
func (s *NewsService) GetNews(stockName string) ([]models.NewsItem, error) {
	cacheKey := "news:" + stockName
	if cached, ok := s.cache.Get(cacheKey); ok {
		return cached.([]models.NewsItem), nil
	}

	items, err := s.fetchNaver(stockName)
	if err != nil {
		return nil, err
	}

	items = dedupByTitle(items)
	s.cache.Set(cacheKey, items, 3*time.Minute)
	log.Printf("네이버 뉴스 조회: 종목=%s, 건수=%d", stockName, len(items))
	return items, nil
}

// fetchNaver 네이버 뉴스 검색 API 호출 (최신순 10건)
func (s *NewsService) fetchNaver(query string) ([]models.NewsItem, error) {
	if s.clientID == "" {
		return nil, fmt.Errorf("NAVER_CLIENT_ID가 설정되지 않았습니다")
	}

	endpoint := "https://openapi.naver.com/v1/search/news.json"
	params := url.Values{}
	params.Set("query", query)
	params.Set("display", "20") // 중복 제거 후 10건 확보를 위해 여유 있게 조회
	params.Set("sort", "date")

	req, err := http.NewRequest("GET", endpoint+"?"+params.Encode(), nil)
	if err != nil {
		return nil, fmt.Errorf("네이버 뉴스 요청 생성 실패: %w", err)
	}
	req.Header.Set("X-Naver-Client-Id", s.clientID)
	req.Header.Set("X-Naver-Client-Secret", s.clientSecret)

	resp, err := s.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("네이버 뉴스 API 요청 실패: %w", err)
	}
	defer resp.Body.Close()

	data, _ := io.ReadAll(resp.Body)

	var result struct {
		Items []struct {
			Title       string `json:"title"`
			Link        string `json:"link"`
			PubDate     string `json:"pubDate"`
			OriginalLink string `json:"originallink"`
		} `json:"items"`
	}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, fmt.Errorf("네이버 뉴스 API 파싱 실패: %w", err)
	}

	items := make([]models.NewsItem, 0, len(result.Items))
	for _, it := range result.Items {
		link := it.OriginalLink
		if link == "" {
			link = it.Link
		}
		items = append(items, models.NewsItem{
			Title:       stripHTML(it.Title),
			URL:         link,
			PublishedAt: it.PubDate,
			Source:      extractDomain(link),
		})
	}
	return items, nil
}

// htmlTagRe HTML 태그 제거용 정규식
var htmlTagRe = regexp.MustCompile(`<[^>]+>`)

// stripHTML HTML 태그 및 엔티티 제거
func stripHTML(s string) string {
	s = htmlTagRe.ReplaceAllString(s, "")
	s = strings.ReplaceAll(s, "&amp;", "&")
	s = strings.ReplaceAll(s, "&lt;", "<")
	s = strings.ReplaceAll(s, "&gt;", ">")
	s = strings.ReplaceAll(s, "&quot;", "\"")
	s = strings.ReplaceAll(s, "&#39;", "'")
	return strings.TrimSpace(s)
}

// extractDomain URL에서 도메인명만 추출
func extractDomain(rawURL string) string {
	u, err := url.Parse(rawURL)
	if err != nil {
		return ""
	}
	host := u.Hostname()
	// www. 제거
	host = strings.TrimPrefix(host, "www.")
	return host
}

// normalizeTitle 제목을 소문자·공백 제거하여 중복 비교용 키 생성
func normalizeTitle(title string) string {
	var b strings.Builder
	for _, r := range strings.ToLower(title) {
		if !unicode.IsSpace(r) && !unicode.IsPunct(r) {
			b.WriteRune(r)
		}
	}
	return b.String()
}

// dedupByTitle 정규화된 제목 기준으로 중복 기사 제거
func dedupByTitle(items []models.NewsItem) []models.NewsItem {
	seen := make(map[string]struct{}, len(items))
	out := make([]models.NewsItem, 0, len(items))
	for _, item := range items {
		key := normalizeTitle(item.Title)
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, item)
		if len(out) == 10 {
			break
		}
	}
	return out
}