package app

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

	cos "github.com/tencentyun/cos-go-sdk-v5"
)

const cvmCAMCredentialMetadataBaseURL = "http://metadata.tencentyun.com/latest/meta-data/cam/security-credentials"

type cvmCAMCredential struct {
	TmpSecretID  string `json:"TmpSecretId"`
	TmpSecretKey string `json:"TmpSecretKey"`
	Token        string `json:"Token"`
	ExpiredTime  int64  `json:"ExpiredTime"`
	Code         string `json:"Code"`
}

// cvmCAMCredentialProvider 在凭证过期前 5 分钟刷新，整个结构不暴露凭证方法，避免误打日志。
type cvmCAMCredentialProvider struct {
	roleName string
	client   *http.Client
	mu       sync.Mutex
	cached   cvmCAMCredential
}

func newCVMCAMCredentialProvider(roleName string) *cvmCAMCredentialProvider {
	return &cvmCAMCredentialProvider{
		roleName: strings.TrimSpace(roleName),
		// metadata 是启动/请求链路的外部依赖，必须在 2 秒内失败，不能把 COS 归档 goroutine 长时间卡死。
		client: &http.Client{Timeout: 2 * time.Second},
	}
}

func (p *cvmCAMCredentialProvider) get(ctx context.Context) (string, string, string, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	now := time.Now().UTC().Unix()
	if p.cached.ExpiredTime > now+int64((5*time.Minute).Seconds()) {
		return p.cached.TmpSecretID, p.cached.TmpSecretKey, p.cached.Token, nil
	}
	previous := p.cached
	if p.roleName == "" {
		return "", "", "", fmt.Errorf("wallet outbox archive CVM CAM role name is empty")
	}
	metadataURL := cvmCAMCredentialMetadataBaseURL + "/" + url.PathEscape(p.roleName)
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataURL, nil)
	if err != nil {
		return "", "", "", err
	}
	response, err := p.client.Do(request)
	if err != nil {
		if previous.ExpiredTime > now {
			return previous.TmpSecretID, previous.TmpSecretKey, previous.Token, nil
		}
		return "", "", "", fmt.Errorf("load wallet outbox archive CVM role credential: %w", err)
	}
	defer response.Body.Close()
	if response.StatusCode < 200 || response.StatusCode > 299 {
		// 不读取/回传 metadata 错误体，避免平台异常响应携带临时凭证。
		if previous.ExpiredTime > now {
			return previous.TmpSecretID, previous.TmpSecretKey, previous.Token, nil
		}
		return "", "", "", fmt.Errorf("load wallet outbox archive CVM role credential: metadata status %d", response.StatusCode)
	}
	var credential cvmCAMCredential
	if err := json.NewDecoder(response.Body).Decode(&credential); err != nil {
		return "", "", "", fmt.Errorf("decode wallet outbox archive CVM role credential: %w", err)
	}
	if credential.Code != "Success" || strings.TrimSpace(credential.TmpSecretID) == "" || strings.TrimSpace(credential.TmpSecretKey) == "" || strings.TrimSpace(credential.Token) == "" || credential.ExpiredTime <= now+30 {
		return "", "", "", fmt.Errorf("wallet outbox archive CVM role returned incomplete or expired credential")
	}
	p.cached = credential
	return credential.TmpSecretID, credential.TmpSecretKey, credential.Token, nil
}

// cvmRoleAuthorizationTransport 每次签名前从缓存 provider 取 STS；只有刷新窗口才访问 metadata。
type cvmRoleAuthorizationTransport struct {
	provider  *cvmCAMCredentialProvider
	transport http.RoundTripper
}

func (t *cvmRoleAuthorizationTransport) GetCredential() (string, string, string, error) {
	return t.provider.get(context.Background())
}

func (t *cvmRoleAuthorizationTransport) RoundTrip(request *http.Request) (*http.Response, error) {
	secretID, secretKey, token, err := t.provider.get(request.Context())
	if err != nil {
		return nil, err
	}
	return (&cos.AuthorizationTransport{
		SecretID: secretID, SecretKey: secretKey, SessionToken: token, Transport: t.transport,
	}).RoundTrip(request)
}
