package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"stocksearch/config"
	"stocksearch/models"
	"sync"
	"time"
)

// TokenService 키움증권 액세스 토큰 관리 (싱글턴)
type TokenService struct {
	mu    sync.RWMutex
	token *models.TokenResponse
}

var tokenSvc *TokenService
var tokenOnce sync.Once

// GetTokenService 토큰 서비스 싱글턴 반환
func GetTokenService() *TokenService {
	tokenOnce.Do(func() {
		tokenSvc = &TokenService{}
	})
	return tokenSvc
}

// Start 서버 시작 시 토큰 발급 후 자동 갱신 고루틴 실행
func (s *TokenService) Start() error {
	if err := s.refresh(); err != nil {
		return fmt.Errorf("초기 토큰 발급 실패: %w", err)
	}
	go s.autoRefresh()
	return nil
}

// GetToken 현재 유효한 액세스 토큰 반환
func (s *TokenService) GetToken() string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.token == nil {
		return ""
	}
	return s.token.Token
}

// refresh 키움증권 API로 토큰 발급
func (s *TokenService) refresh() error {
	cfg := config.App
	body := map[string]string{
		"grant_type": "client_credentials",
		"appkey":     cfg.AppKey,
		"secretkey":  cfg.AppSecret,
	}
	data, _ := json.Marshal(body)

	resp, err := http.Post(
		cfg.BaseURL+"/oauth2/token",
		"application/json;charset=UTF-8",
		bytes.NewReader(data),
	)
	if err != nil {
		return fmt.Errorf("토큰 발급 요청 실패: %w", err)
	}
	defer resp.Body.Close()

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

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("토큰 발급 응답 오류: HTTP %d, 응답: %s", resp.StatusCode, string(respBody))
	}

	// HTML 응답 시 서버 점검 중으로 판단
	contentType := resp.Header.Get("Content-Type")
	if len(respBody) > 0 && respBody[0] == '<' || (contentType != "" && !bytes.Contains([]byte(contentType), []byte("json"))) {
		return fmt.Errorf("키움증권 서버 점검 중 (HTML 응답): %s", contentType)
	}

	var tokenResp models.TokenResponse
	if err := json.Unmarshal(respBody, &tokenResp); err != nil {
		return fmt.Errorf("토큰 응답 파싱 실패: %w", err)
	}
	if tokenResp.ReturnCode != 0 {
		return fmt.Errorf("토큰 발급 실패: %s", tokenResp.ReturnMsg)
	}

	// expires_dt: YYYYMMDDHHmmss 형식 파싱
	expiresTime, err := time.ParseInLocation("20060102150405", tokenResp.ExpiresAt, time.Local)
	if err != nil {
		// 파싱 실패 시 12시간 후를 기본값으로 설정
		expiresTime = time.Now().Add(12 * time.Hour)
	}
	tokenResp.ExpiresTime = expiresTime

	s.mu.Lock()
	s.token = &tokenResp
	s.mu.Unlock()

	log.Printf("토큰 발급 완료 (만료: %s)", expiresTime.Format("2006-01-02 15:04:05"))
	return nil
}

// autoRefresh 만료 1시간 전에 자동으로 토큰 갱신
func (s *TokenService) autoRefresh() {
	for {
		s.mu.RLock()
		expiresTime := s.token.ExpiresTime
		s.mu.RUnlock()

		// 만료 1시간 전에 갱신
		refreshAt := expiresTime.Add(-1 * time.Hour)
		waitDuration := time.Until(refreshAt)
		if waitDuration < 0 {
			waitDuration = 10 * time.Second // 이미 만료됐으면 즉시 재시도
		}

		time.Sleep(waitDuration)

		if err := s.refresh(); err != nil {
			log.Printf("토큰 자동 갱신 실패: %v (10초 후 재시도)", err)
			time.Sleep(10 * time.Second)
		}
	}
}
