package weixin

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"log/slog"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	"github.com/chenhg5/cc-connect/core"
)

// formatAesKeyForAPI encodes a raw AES key as base64(hex_string),
// matching the format expected by the WeChat iLink sendMessage API.
func formatAesKeyForAPI(key []byte) string {
	return base64.StdEncoding.EncodeToString([]byte(hex.EncodeToString(key)))
}

// isWeixinCDNHost 检查 URL 是否指向已知的微信国内 CDN 域名
func isWeixinCDNHost(rawURL string) bool {
	u, err := url.Parse(rawURL)
	if err != nil {
		return false
	}
	host := strings.ToLower(u.Hostname())
	return strings.HasSuffix(host, ".weixin.qq.com") || strings.HasSuffix(host, ".wechat.com")
}

type cdnUploadedRef struct {
	downloadParam string
	aesKey        []byte
	cipherSize    int
	rawSize       int
}

func (p *Platform) resolveReplyContext(replyCtx any) (*replyContext, error) {
	rc, ok := replyCtx.(*replyContext)
	if !ok || rc == nil {
		return nil, fmt.Errorf("weixin: invalid reply context")
	}
	if strings.TrimSpace(rc.contextToken) == "" {
		rc.contextToken = p.getContextToken(rc.peerUserID)
	}
	if strings.TrimSpace(rc.contextToken) == "" {
		return nil, fmt.Errorf("weixin: missing context_token for peer %q", rc.peerUserID)
	}
	return rc, nil
}

func (p *Platform) uploadToWeixinCDN(ctx context.Context, to string, plaintext []byte, mediaType int, label string) (*cdnUploadedRef, error) {
	if len(plaintext) == 0 {
		return nil, fmt.Errorf("weixin: %s: empty payload", label)
	}
	if strings.TrimSpace(p.cdnBaseURL) == "" {
		return nil, fmt.Errorf("weixin: cdn_base_url is empty")
	}
	rawSize := len(plaintext)
	aesKey := make([]byte, 16)
	if _, err := rand.Read(aesKey); err != nil {
		return nil, fmt.Errorf("weixin: %s: aes key: %w", label, err)
	}
	filekey := randomHex(16)
	req := getUploadURLRequest{
		Filekey:     filekey,
		MediaType:   mediaType,
		ToUserID:    to,
		Rawsize:     rawSize,
		Rawfilemd5:  md5Hex(plaintext),
		Filesize:    aesECBPaddedSize(rawSize),
		NoNeedThumb: true,
		Aeskey:      hex.EncodeToString(aesKey),
	}
	resp, err := p.api.getUploadURL(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("weixin: %s: %w", label, err)
	}
	// 选择上传 URL 和 HTTP client
	var cdnUploadURL string
	var uploadClient *http.Client
	if resp.UploadFullURL != "" {
		// 新版 API：使用服务端返回的完整 URL
		cdnUploadURL = resp.UploadFullURL
		// 如果 URL 指向已知的微信国内 CDN，使用无代理 client 直连
		if isWeixinCDNHost(cdnUploadURL) {
			uploadClient = p.cdnHttpClient
		} else {
			uploadClient = p.httpClient
		}
	} else {
		// 旧版 API：用 upload_param 构建 URL，使用配置的 httpClient
		cdnUploadURL = buildCdnUploadURL(p.cdnBaseURL, resp.UploadParam, filekey)
		uploadClient = p.httpClient
	}
	dl, err := uploadBufferToCDN(ctx, uploadClient, cdnUploadURL, plaintext, aesKey, label)
	if err != nil {
		return nil, err
	}
	return &cdnUploadedRef{
		downloadParam: dl,
		aesKey:        aesKey,
		cipherSize:    aesECBPaddedSize(rawSize),
		rawSize:       rawSize,
	}, nil
}

func (p *Platform) sendSingleItem(ctx context.Context, rc *replyContext, item messageItem) error {
	return p.sendSingleItemWithRetry(ctx, rc, item)
}

func mediaFromUploadRef(ref *cdnUploadedRef) *cdnMedia {
	return &cdnMedia{
		EncryptQueryParam: ref.downloadParam,
		AESKey:            formatAesKeyForAPI(ref.aesKey),
		EncryptType:       1,
	}
}

func buildVideoMessageItem(ref *cdnUploadedRef) messageItem {
	return messageItem{
		Type: messageItemVideo,
		VideoItem: &videoItem{
			Media:     mediaFromUploadRef(ref),
			VideoSize: ref.cipherSize,
		},
	}
}

// sendSingleItemWithRetry sends a media item with retry mechanism for ret=-2 errors.
func (p *Platform) sendSingleItemWithRetry(ctx context.Context, rc *replyContext, item messageItem) error {
	var lastErr error
	for attempt := 0; attempt < weixinSendMaxRetries; attempt++ {
		msg := sendMessageReq{
			Msg: weixinOutboundMsg{
				FromUserID:   "",
				ToUserID:     rc.peerUserID,
				ClientID:     "cc-" + randomHex(8),
				MessageType:  messageTypeBot,
				MessageState: messageStateFinish,
				ItemList:     []messageItem{item},
				ContextToken: rc.contextToken,
			},
		}
		err := p.api.sendMessage(ctx, &msg)
		if err == nil {
			return nil
		}
		lastErr = err
		// Check if error is ret=-2 (API declined) - attempt token refresh
		if strings.Contains(err.Error(), "ret=-2") {
			freshToken := p.getContextToken(rc.peerUserID)
			if freshToken == "" || freshToken == rc.contextToken {
				slog.Warn("weixin: sendMessage ret=-2 for media, no fresh context_token — "+
					"user must send a new message to refresh session token",
					"attempt", attempt+1, "peer", rc.peerUserID)
				return fmt.Errorf("weixin: sendMessage ret=-2 (expired context_token); "+
					"user must send a new message to peer %q to refresh the session token: %w",
					rc.peerUserID, lastErr)
			}
			slog.Warn("weixin: sendMessage ret=-2 for media, retrying with fresh context_token",
				"attempt", attempt+1, "peer", rc.peerUserID)
			rc.contextToken = freshToken
			slog.Debug("weixin: using refreshed context_token for media retry", "peer", rc.peerUserID)
			// Brief delay before retry
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(weixinSendRetryDelay):
			}
			continue
		}
		// For other errors, don't retry
		return err
	}
	return lastErr
}

// SendImage implements core.ImageSender.
func (p *Platform) SendImage(ctx context.Context, replyCtx any, img core.ImageAttachment) error {
	rc, err := p.resolveReplyContext(replyCtx)
	if err != nil {
		return err
	}
	if len(img.Data) == 0 {
		return fmt.Errorf("weixin: empty image")
	}
	ref, err := p.uploadToWeixinCDN(ctx, rc.peerUserID, img.Data, uploadMediaImage, "SendImage")
	if err != nil {
		return err
	}
	item := messageItem{
		Type: messageItemImage,
		ImageItem: &imageItem{
			Media: &cdnMedia{
				EncryptQueryParam: ref.downloadParam,
				AESKey:            formatAesKeyForAPI(ref.aesKey),
				EncryptType:       1,
			},
			MidSize: ref.cipherSize,
		},
	}
	return p.sendSingleItem(ctx, rc, item)
}

// SendFile implements core.FileSender.
func (p *Platform) SendFile(ctx context.Context, replyCtx any, file core.FileAttachment) error {
	rc, err := p.resolveReplyContext(replyCtx)
	if err != nil {
		return err
	}
	if len(file.Data) == 0 {
		return fmt.Errorf("weixin: empty file")
	}
	name := strings.TrimSpace(file.FileName)
	if name == "" {
		name = "file.bin"
	}

	if isVideoFile(file) {
		ref, err := p.uploadToWeixinCDN(ctx, rc.peerUserID, file.Data, uploadMediaVideo, "SendFileVideo")
		if err != nil {
			return err
		}
		return p.sendSingleItem(ctx, rc, buildVideoMessageItem(ref))
	}

	ref, err := p.uploadToWeixinCDN(ctx, rc.peerUserID, file.Data, uploadMediaFile, "SendFile")
	if err != nil {
		return err
	}
	item := messageItem{
		Type: messageItemFile,
		FileItem: &fileItem{
			Media: &cdnMedia{
				EncryptQueryParam: ref.downloadParam,
				AESKey:            formatAesKeyForAPI(ref.aesKey),
				EncryptType:       1,
			},
			FileName: name,
			Len:      fmt.Sprintf("%d", ref.rawSize),
		},
	}
	return p.sendSingleItem(ctx, rc, item)
}

func isVideoFile(file core.FileAttachment) bool {
	mime := strings.ToLower(strings.TrimSpace(file.MimeType))
	if strings.HasPrefix(mime, "video/") {
		return true
	}
	ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(file.FileName)), ".")
	switch ext {
	case "avi", "m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "webm":
		return true
	default:
		return false
	}
}

// SendAudio implements core.AudioSender.
// Weixin voice messages require AMR or SILK format. Since SILK encoding is not
// widely supported, we convert to AMR format using ffmpeg.
func (p *Platform) SendAudio(ctx context.Context, replyCtx any, audio []byte, format string) error {
	rc, err := p.resolveReplyContext(replyCtx)
	if err != nil {
		return err
	}
	if len(audio) == 0 {
		return fmt.Errorf("weixin: empty audio")
	}

	// Convert to AMR format if not already AMR
	sendData := audio
	sendFormat := strings.ToLower(strings.TrimSpace(format))
	if sendFormat == "" {
		sendFormat = "wav" // TTS typically outputs WAV
	}
	if sendFormat != "amr" {
		converted, err := core.ConvertAudioToAMR(ctx, audio, sendFormat)
		if err != nil {
			return fmt.Errorf("weixin: convert %s to AMR: %w", sendFormat, err)
		}
		sendData = converted
		sendFormat = "amr"
	}

	slog.Debug("weixin: audio converted", "format", sendFormat, "size", len(sendData))

	// Upload to CDN as file type (voice uses same CDN upload mechanism)
	ref, err := p.uploadToWeixinCDN(ctx, rc.peerUserID, sendData, uploadMediaFile, "SendAudio")
	if err != nil {
		return err
	}

	// Send as voice message
	item := messageItem{
		Type: messageItemVoice,
		VoiceItem: &voiceItem{
			Media: &cdnMedia{
				EncryptQueryParam: ref.downloadParam,
				AESKey:            formatAesKeyForAPI(ref.aesKey),
				EncryptType:       1,
			},
			EncodeType: 0, // 0 = AMR format, 1 = SILK format
		},
	}
	return p.sendSingleItem(ctx, rc, item)
}
