package service

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/spf13/viper"

	"finance-portal/pkg/errors"
	"finance-portal/pkg/log"
)

type TokenInfo struct {
	Token  string
	Expiry time.Time
}

type TokenCache struct {
	tokens map[string]*TokenInfo
	mutex  sync.RWMutex
}

// 初始化 TokenCache
var tokenCache = TokenCache{
	tokens: make(map[string]*TokenInfo),
}

func (cache *TokenCache) GetToken(ctx context.Context, service string) (string, error) {
	// 读取缓存
	cache.mutex.RLock()
	tokenInfo, exists := cache.tokens[service]
	cache.mutex.RUnlock()

	if exists && tokenInfo.Token != "" && time.Now().Before(tokenInfo.Expiry) {
		log.L(ctx).Info("Token exists in cache.")

		return tokenInfo.Token, nil
	}

	// 获取新 Token
	newToken, expiry, err := fetchNewToken(ctx, service)
	if err != nil {
		log.L(ctx).Errorf("Error when fetch new token: %s", err.Error())

		return "", err
	}

	// 更新缓存
	cache.mutex.Lock()
	cache.tokens[service] = &TokenInfo{
		Token:  newToken,
		Expiry: expiry,
	}
	cache.mutex.Unlock()

	return newToken, nil
}

func fetchNewToken(ctx context.Context, service string) (string, time.Time, error) {
	switch service {
	case "lark":
		return fetchLarkToken(ctx)
	case "salesforce":
		return fetchSalesforceToken(ctx)
	case "nexuscrm":
		return fetchNexusCRMToken(ctx)
	case "fivetran":
		return fetchFivetranToken(ctx)
	default:
		log.L(ctx).Errorf("Unknown service: %s", service)

		return "", time.Time{}, fmt.Errorf("unknown service: %s", service)
	}
}

func fetchLarkToken(ctx context.Context) (string, time.Time, error) {
	log.L(ctx).Info("Fetching Lark Token.")

	appID := viper.GetString("lark.id")
	appSecret := viper.GetString("lark.secret")
	if appID == "" || appSecret == "" {
		log.L(ctx).Error("Lark app ID or secret is empty.")

		return "", time.Time{}, errors.New("lark app ID or secret is empty")
	}

	// 构造请求体
	body, err := json.Marshal(map[string]string{
		"app_id":     appID,
		"app_secret": appSecret,
	})
	if err != nil {
		log.L(ctx).Errorf("Error when marshal lark request body: %s", err.Error())

		return "", time.Time{}, err
	}

	reqBody := bytes.NewBuffer(body)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", reqBody)
	if err != nil {
		log.L(ctx).Errorf("Error when create lark request: %s", err.Error())

		return "", time.Time{}, err
	}

	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(ctx).Errorf("Error when request lark tenant access token: %s", err.Error())

		return "", time.Time{}, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		log.L(ctx).Errorf("Error when read lark response body: %s", err.Error())

		return "", time.Time{}, err
	}

	var res struct {
		TAT string `json:"tenant_access_token"`
	}
	if err := json.Unmarshal(respBody, &res); err != nil {
		log.L(ctx).Errorf("Error when unmarshal lark res: %s", err.Error())

		return "", time.Time{}, err
	}

	return res.TAT, time.Now().Add(30 * time.Minute), nil
}

func fetchSalesforceToken(ctx context.Context) (string, time.Time, error) {
	log.L(ctx).Info("Fetching Salesforce Token.")

	domain := viper.GetString("sf.tokenDomain")
	if domain == "" {
		err := errors.New("sf.tokenDomain is not configured")
		log.L(ctx).Errorf("Salesforce token domain is not configured", "error", err)

		return "", time.Time{}, err
	}

	url := fmt.Sprintf("https://%s/services/oauth2/token", domain)

	body := buildSalesforceTokenBody(
		viper.GetString("sf.clientId"),
		viper.GetString("sf.clientSecret"),
		viper.GetString("sf.username"),
		viper.GetString("sf.password"),
	)

	resp, err := http.Post(url, "application/x-www-form-urlencoded", bytes.NewReader([]byte(body)))
	if err != nil {
		log.L(ctx).Errorf("Failed to request Salesforce token", "error", err)

		return "", time.Time{}, fmt.Errorf("failed to request Salesforce token: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		errorMsg := fmt.Sprintf("failed to get token, status: %d, response: %s", resp.StatusCode, string(respBody))
		log.L(ctx).Errorf("Failed to get Salesforce token", "error", errorMsg)

		return "", time.Time{}, errors.New(errorMsg)
	}

	var res sfTokenResp
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		log.L(ctx).Errorf("Failed to decode Salesforce token response", "error", err)

		return "", time.Time{}, fmt.Errorf("failed to decode Salesforce token response: %w", err)
	}

	if res.AccessToken == "" {
		log.L(ctx).Errorf("Salesforce returned empty access token", "response", res)

		return "", time.Time{}, errors.New("Salesforce returned empty access token")
	}

	// 计算 Token 的过期时间
	expiry := time.Now().Add(1 * time.Hour)

	log.L(ctx).Info("Salesforce Token fetched.")

	return res.AccessToken, expiry, nil
}

func buildSalesforceTokenBody(clientID, clientSecret, username, password string) string {
	values := url.Values{}
	values.Set("grant_type", "password")
	values.Set("client_id", clientID)
	values.Set("client_secret", clientSecret)
	values.Set("username", username)
	values.Set("password", password)

	return values.Encode()
}

func fetchNexusCRMToken(ctx context.Context) (string, time.Time, error) {
	log.L(ctx).Info("Fetching NexusCRM Token.")

	baseURL := strings.TrimRight(viper.GetString("nexuscrm.base_url"), "/")
	if baseURL == "" {
		err := errors.New("nexuscrm.base_url is not configured")
		log.L(ctx).Errorf("NexusCRM base URL is not configured", "error", err)

		return "", time.Time{}, err
	}

	clientID := viper.GetString("nexuscrm.client_id")
	if clientID == "" {
		err := errors.New("nexuscrm.client_id is not configured")
		log.L(ctx).Errorf("NexusCRM client ID is not configured", "error", err)

		return "", time.Time{}, err
	}

	secretEnv := viper.GetString("nexuscrm.client_secret_env")
	if secretEnv == "" {
		err := errors.New("nexuscrm.client_secret_env is not configured")
		log.L(ctx).Errorf("NexusCRM client secret env is not configured", "error", err)

		return "", time.Time{}, err
	}

	clientSecret := os.Getenv(secretEnv)
	if clientSecret == "" {
		err := fmt.Errorf("%s is not set", secretEnv)
		log.L(ctx).Errorf("NexusCRM client secret is not available from environment", "error", err)

		return "", time.Time{}, err
	}

	body := buildNexusCRMTokenBody(clientID, clientSecret, viper.GetString("nexuscrm.scope"))
	req, err := http.NewRequestWithContext(
		ctx,
		http.MethodPost,
		baseURL+"/services/oauth2/token",
		bytes.NewReader([]byte(body)),
	)
	if err != nil {
		log.L(ctx).Errorf("Failed to create NexusCRM token request", "error", err)

		return "", time.Time{}, fmt.Errorf("failed to create NexusCRM token request: %w", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(ctx).Errorf("Failed to request NexusCRM token", "error", err)

		return "", time.Time{}, fmt.Errorf("failed to request NexusCRM token: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		errorMsg := fmt.Sprintf("failed to get NexusCRM token, status: %d, response: %s", resp.StatusCode, string(respBody))
		log.L(ctx).Errorf("Failed to get NexusCRM token", "error", errorMsg)

		return "", time.Time{}, errors.New(errorMsg)
	}

	var res sfTokenResp
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		log.L(ctx).Errorf("Failed to decode NexusCRM token response", "error", err)

		return "", time.Time{}, fmt.Errorf("failed to decode NexusCRM token response: %w", err)
	}

	if res.AccessToken == "" {
		log.L(ctx).Errorf("NexusCRM returned empty access token", "response", res)

		return "", time.Time{}, errors.New("NexusCRM returned empty access token")
	}

	log.L(ctx).Info("NexusCRM Token fetched.")

	return res.AccessToken, time.Now().Add(1 * time.Hour), nil
}

func buildNexusCRMTokenBody(clientID, clientSecret, scope string) string {
	values := url.Values{}
	values.Set("grant_type", "client_credentials")
	values.Set("client_id", clientID)
	values.Set("client_secret", clientSecret)
	if scope != "" {
		values.Set("scope", scope)
	}

	return values.Encode()
}

func fetchFivetranToken(ctx context.Context) (string, time.Time, error) {
	// 实现 Fivetran Token 的获取逻辑
	// 例如，通过 Fivetran API 获取 Token

	// 示例实现（请根据实际 API 调整）
	token := "dummy_fivetran_token"
	expiry := time.Now().Add(45 * time.Minute)
	return token, expiry, nil
}
