package wecom

import (
	"bytes"
	"context"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/bytedance/sonic"

	"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/storage"
)

// ApprovalFileReference 是业务快照中可上传到企微的对象存储引用。
type ApprovalFileReference struct {
	StorageKey string `json:"storage_key"`
	FileName   string `json:"file_name"`
}

// ApprovalAttachmentUploader 将对象存储文件上传为企微临时素材。
type ApprovalAttachmentUploader struct {
	tokens      DirectoryTokenProvider
	integration TokenIntegrationLog
	storage     *storage.Service
	httpClient  *http.Client
	baseURL     string
	now         func() time.Time
}

// NewApprovalAttachmentUploader 创建企业微信审批附件上传客户端。
func NewApprovalAttachmentUploader(tokens DirectoryTokenProvider, integration TokenIntegrationLog, storageService *storage.Service, baseURL string, timeout time.Duration) *ApprovalAttachmentUploader {
	if timeout <= 0 {
		timeout = constants.WeComDefaultHTTPTimeout
	}
	return &ApprovalAttachmentUploader{
		tokens: tokens, integration: integration, storage: storageService,
		httpClient: &http.Client{Timeout: timeout}, baseURL: strings.TrimRight(baseURL, "/"), now: time.Now,
	}
}

// Upload 下载对象存储文件并上传为企微临时素材；Integration Log 不记录文件正文或 media_id。
func (u *ApprovalAttachmentUploader) Upload(ctx context.Context, applicationID, instanceID uint, reference ApprovalFileReference) (string, error) {
	if u == nil || u.tokens == nil || u.integration == nil || u.storage == nil || u.httpClient == nil {
		return "", errors.New(errors.CodeServiceUnavailable, "企业微信审批附件上传服务未配置")
	}
	reference.StorageKey = strings.TrimSpace(reference.StorageKey)
	if reference.StorageKey == "" {
		return "", errors.New(errors.CodeInvalidParam, "审批附件对象存储 Key 不能为空")
	}
	localPath, cleanup, err := u.storage.DownloadToTemp(ctx, reference.StorageKey)
	if err != nil {
		return "", errors.Wrap(errors.CodeServiceUnavailable, err, "下载审批附件失败")
	}
	defer cleanup()
	file, err := os.Open(localPath)
	if err != nil {
		return "", errors.Wrap(errors.CodeServiceUnavailable, err, "打开审批附件失败")
	}
	defer file.Close()
	info, err := file.Stat()
	if err != nil || info.Size() <= 5 || info.Size() > constants.WeComApprovalMaxAttachmentBytes {
		return "", errors.New(errors.CodeInvalidParam, "审批附件大小必须大于 5 字节且不超过 20MB")
	}
	fileName := filepath.Base(strings.TrimSpace(reference.FileName))
	if fileName == "." || fileName == "" {
		fileName = filepath.Base(reference.StorageKey)
	}
	token, err := u.tokens.GetAccessToken(ctx, applicationID)
	if err != nil {
		return "", err
	}
	endpoint, err := url.Parse(u.baseURL + "/cgi-bin/media/upload")
	if err != nil || endpoint.Scheme == "" || endpoint.Host == "" {
		return "", errors.New(errors.CodeWeComCredentialInvalid, "企业微信 API 地址配置无效")
	}
	query := endpoint.Query()
	query.Set("access_token", token)
	query.Set("type", "file")
	endpoint.RawQuery = query.Encode()
	request, err := newAttachmentUploadRequest(ctx, endpoint.String(), fileName, file)
	if err != nil {
		return "", err
	}
	resourceID := strconv.FormatUint(uint64(instanceID), 10)
	attempt, err := u.integration.Start(ctx, integrationlog.Attempt{
		Provider: constants.IntegrationProviderWeCom, Direction: constants.IntegrationDirectionOutbound,
		Operation: constants.IntegrationOperationWeComAttachmentUpload, ResourceType: constants.WeComApprovalInstanceResourceType,
		ResourceID: &resourceID, RequestSummary: map[string]any{
			"application_id": applicationID, "file_name": fileName, "file_bytes": info.Size(),
		},
	})
	if err != nil {
		return "", err
	}
	startedAt := u.now()
	response, requestErr := u.httpClient.Do(request)
	if requestErr != nil {
		message := "企业微信审批附件上传失败"
		_, _ = u.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
			Result: constants.IntegrationResultFailed, ProviderCode: "request_failed", ProviderMessage: message,
			ResponseSummary: map[string]any{"success": false}, DurationMS: u.now().Sub(startedAt).Milliseconds(),
		})
		return "", errors.Wrap(errors.CodeServiceUnavailable, requestErr, message)
	}
	defer response.Body.Close()
	body, err := io.ReadAll(io.LimitReader(response.Body, constants.WeComMaxResponseBodyBytes+1))
	var result struct {
		ErrCode int64  `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
		MediaID string `json:"media_id"`
	}
	if err != nil || int64(len(body)) > constants.WeComMaxResponseBodyBytes || sonic.Unmarshal(body, &result) != nil ||
		response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices || result.ErrCode != 0 || strings.TrimSpace(result.MediaID) == "" {
		message := strings.TrimSpace(result.ErrMsg)
		if message == "" {
			message = "企业微信审批附件上传响应无效"
		}
		_, _ = u.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
			Result: constants.IntegrationResultFailed, HTTPStatus: response.StatusCode,
			ProviderCode: strconv.FormatInt(result.ErrCode, 10), ProviderMessage: message,
			ResponseSummary: map[string]any{"success": false, "errcode": result.ErrCode}, DurationMS: u.now().Sub(startedAt).Milliseconds(),
		})
		return "", errors.New(errors.CodeServiceUnavailable, "上传企业微信审批附件失败")
	}
	_, err = u.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
		Result: constants.IntegrationResultSuccess, HTTPStatus: response.StatusCode,
		ProviderCode: strconv.FormatInt(result.ErrCode, 10), ProviderMessage: result.ErrMsg,
		ResponseSummary: map[string]any{"success": true, "media_id_set": true}, DurationMS: u.now().Sub(startedAt).Milliseconds(),
	})
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(result.MediaID), nil
}

// newAttachmentUploadRequest 在内存上限受文件大小校验约束的前提下组装 multipart 请求，避免流式写入 goroutine 泄漏。
func newAttachmentUploadRequest(ctx context.Context, endpoint, fileName string, file *os.File) (*http.Request, error) {
	var body bytes.Buffer
	multipartWriter := multipart.NewWriter(&body)
	part, err := multipartWriter.CreateFormFile("media", fileName)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "创建企业微信附件表单失败")
	}
	if _, err := io.Copy(part, file); err != nil {
		return nil, errors.Wrap(errors.CodeServiceUnavailable, err, "读取审批附件失败")
	}
	if err := multipartWriter.Close(); err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "完成企业微信附件表单失败")
	}
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body.Bytes()))
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "创建企业微信附件上传请求失败")
	}
	request.Header.Set("Content-Type", multipartWriter.FormDataContentType())
	return request, nil
}
