package billing

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

	"github.com/songquanpeng/one-api/common/errfilter"
	"github.com/songquanpeng/one-api/common/logger"
	"github.com/songquanpeng/one-api/relay/channeltype"
)

var (
	ruoyiBaseURL = os.Getenv("RUOYI_BASE_URL")
	ruoyiAPIKey  = os.Getenv("RUOYI_API_KEY")
	httpClient   = &http.Client{Timeout: 10 * time.Second}
)

func RuoyiBaseURL() string { return ruoyiBaseURL }
func RuoyiAPIKey() string  { return ruoyiAPIKey }

// ValidateConfig 启动时校验计费配置（在 main.go 启动阶段调用一次）。
// 配置缺失不阻断启动，但所有 preCheck/callback 都会失败，必须靠启动日志暴露。
func ValidateConfig() {
	if ruoyiBaseURL == "" {
		logger.SysError("[Billing] event=config_missing | key=RUOYI_BASE_URL | preCheck/callback will fail")
	} else {
		// ruoyi 地址注册进错误过滤集：preCheck/callback 的报错透出给客户端时
		// 不允许泄露计费系统的内网地址
		errfilter.RegisterBaseURL(ruoyiBaseURL)
	}
	if ruoyiAPIKey == "" {
		logger.SysError("[Billing] event=config_missing | key=RUOYI_API_KEY | preCheck/callback will fail")
	}
}

// ─── 日志约定（billing 包统一走这里）────────────────────────────
// 格式: [Billing] requestId=<id> | event=<事件> | key=value ...

func logInfo(requestID, event string, kvs ...interface{}) {
	logger.Infof(context.Background(), "[Billing] %s", formatLog(requestID, event, kvs...))
}

func logError(requestID, event string, kvs ...interface{}) {
	logger.SysError("[Billing] " + formatLog(requestID, event, kvs...))
}

func formatLog(requestID, event string, kvs ...interface{}) string {
	s := fmt.Sprintf("requestId=%s | event=%s", requestID, event)
	for i := 0; i+1 < len(kvs); i += 2 {
		s += fmt.Sprintf(" | %v=%v", kvs[i], kvs[i+1])
	}
	return s
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "...(truncated)"
}

// ─── 渠道类型 ──────────────────────────────────────────────────

// IsVideoChannel 根据渠道类型判断是否是视频生成
func IsVideoChannel(channelType int) bool {
	return channelType == channeltype.Seedance
}

// GetTaskType 根据渠道类型返回任务类型
// 原第76行：[BUG-5] GetTaskType 只返回 video/chat，网关永远不会发 taskType=image
func GetTaskType(channelType int) string {
	if IsVideoChannel(channelType) {
		return "video"
	}
	return "chat"
}

// CalculateCost 纯计算函数（毫厘），供结算时使用
func CalculateCost(tokens int, pricePerMillion int64) int64 {
	if tokens <= 0 || pricePerMillion <= 0 {
		return 0
	}
	result := int64(tokens) * pricePerMillion
	return (result + 500000) / 1000000
}

// ─── preCheck ──────────────────────────────────────────────────

type PreCheckReq struct {
	TenantID    string `json:"tenantId"`
	Model       string `json:"model"`
	RequestID   string `json:"requestId"`
	InputTokens int    `json:"inputTokens"`
	MaxTokens   int    `json:"maxTokens"`
	TaskType    string `json:"taskType"`
}

type PreCheckResp struct {
	Code int `json:"code"`
	Data struct {
		TenantStatus  bool   `json:"tenantStatus"`
		WalletStatus  bool   `json:"walletStatus"`
		TenantBalance int64  `json:"tenantBalance"`
		InputPrice    int64  `json:"inputPrice"`
		OutputPrice   int64  `json:"outputPrice"`
		EstimatedCost int64  `json:"estimatedCost"`
		Allow         bool   `json:"allow"`
		Message       string `json:"message"`
	} `json:"data"`
}

type CallbackReq struct {
	RequestID string `json:"requestId"`
	TenantID  string `json:"tenantId"`
	Model     string `json:"model"`
	Status    string `json:"status"`
	TaskType  string `json:"taskType"`
	Usage     *Usage `json:"usage,omitempty"`
	VideoURL  string `json:"videoUrl,omitempty"`
	ErrorMsg  string `json:"errorMsg,omitempty"`
	Secret    string `json:"secret"`
}

type Usage struct {
	PromptTokens     int `json:"promptTokens"`
	CompletionTokens int `json:"completionTokens"`
	TotalTokens      int `json:"totalTokens"`
}

func PreCheck(ctx context.Context, req *PreCheckReq) (*PreCheckResp, error) {
	url := ruoyiBaseURL + "/billing/preCheck"
	body, _ := json.Marshal(req)

	httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Authorization", "Bearer "+ruoyiAPIKey)

	resp, err := httpClient.Do(httpReq)
	if err != nil {
		logError(req.RequestID, "precheck_http_error", "error", err.Error())
		return nil, fmt.Errorf("preCheck failed: %w", err)
	}
	defer resp.Body.Close()

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

	if resp.StatusCode != http.StatusOK {
		logError(req.RequestID, "precheck_http_error", "httpStatus", resp.StatusCode, "body", truncate(string(respBody), 300))
		return nil, fmt.Errorf("preCheck failed, status=%d, body=%s", resp.StatusCode, truncate(string(respBody), 300))
	}

	var result PreCheckResp
	if err := json.Unmarshal(respBody, &result); err != nil {
		logError(req.RequestID, "precheck_unmarshal_error", "error", err.Error(), "body", truncate(string(respBody), 300))
		return nil, err
	}
	if result.Code != 200 {
		logError(req.RequestID, "precheck_business_error", "code", result.Code, "msg", result.Data.Message)
		return nil, fmt.Errorf("preCheck error, code=%d, msg=%s", result.Code, result.Data.Message)
	}

	logInfo(req.RequestID, "precheck_result",
		"allow", result.Data.Allow,
		"estimatedCost", result.Data.EstimatedCost,
		"tenantId", req.TenantID,
		"model", req.Model,
		"taskType", req.TaskType)
	return &result, nil
}

// Callback 结算/退款回调。
// 成功记一条 Info，失败记 Error（涉及钱，失败必须留痕）；
// 调用方无需重复记录日志。
func Callback(ctx context.Context, req *CallbackReq) error {
	url := ruoyiBaseURL + "/billing/callback"
	body, _ := json.Marshal(req)

	httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Authorization", "Bearer "+ruoyiAPIKey)

	resp, err := httpClient.Do(httpReq)
	if err != nil {
		logError(req.RequestID, "callback_http_error", "status", req.Status, "taskType", req.TaskType, "error", err.Error())
		return fmt.Errorf("callback failed: %w", err)
	}
	defer resp.Body.Close()

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

	if resp.StatusCode != http.StatusOK {
		logError(req.RequestID, "callback_http_error", "status", req.Status, "taskType", req.TaskType,
			"httpStatus", resp.StatusCode, "body", truncate(string(respBody), 300))
		return fmt.Errorf("callback failed, status=%d, body=%s", resp.StatusCode, truncate(string(respBody), 300))
	}

	var result struct {
		Code int    `json:"code"`
		Msg  string `json:"msg"`
	}
	if err := json.Unmarshal(respBody, &result); err != nil {
		logError(req.RequestID, "callback_unmarshal_error", "status", req.Status, "error", err.Error(), "body", truncate(string(respBody), 300))
		return err
	}
	if result.Code != 200 {
		logError(req.RequestID, "callback_business_error", "status", req.Status, "code", result.Code, "msg", result.Msg)
		return fmt.Errorf("callback error, code=%d, msg=%s", result.Code, result.Msg)
	}

	logInfo(req.RequestID, "callback_success", "status", req.Status, "taskType", req.TaskType, "model", req.Model, "tenantId", req.TenantID)
	return nil
}
