package wecom

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/bytedance/sonic"

	wecomapp "github.com/break/junhong_cmp_fiber/internal/application/wecom"
	"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/middleware"
)

// TemplateClient 读取企微后台已有审批模板的控件结构。
type TemplateClient struct {
	tokens      DirectoryTokenProvider
	integration TokenIntegrationLog
	httpClient  *http.Client
	baseURL     string
	now         func() time.Time
}

// NewTemplateClient 创建企业微信审批模板详情客户端。
func NewTemplateClient(tokens DirectoryTokenProvider, integration TokenIntegrationLog, baseURL string, timeout time.Duration) *TemplateClient {
	if timeout <= 0 {
		timeout = constants.WeComDefaultHTTPTimeout
	}
	return &TemplateClient{
		tokens: tokens, integration: integration, httpClient: &http.Client{Timeout: timeout},
		baseURL: strings.TrimRight(baseURL, "/"), now: time.Now,
	}
}

// GetTemplateDetail 获取模板详情并只投影控件结构，不保存审批节点和审批人规则。
func (c *TemplateClient) GetTemplateDetail(ctx context.Context, applicationID uint, templateID string) (wecomapp.TemplateDefinition, error) {
	if c == nil || c.tokens == nil || c.integration == nil || c.httpClient == nil {
		return wecomapp.TemplateDefinition{}, errors.New(errors.CodeServiceUnavailable, "企业微信模板服务未配置")
	}
	token, err := c.tokens.GetAccessToken(ctx, applicationID)
	if err != nil {
		return wecomapp.TemplateDefinition{}, err
	}
	request, err := c.newRequest(ctx, token, templateID)
	if err != nil {
		return wecomapp.TemplateDefinition{}, err
	}
	resourceID := strconv.FormatUint(uint64(applicationID), 10)
	requestID := middleware.GetRequestIDFromContext(ctx)
	attempt, err := c.integration.Start(ctx, integrationlog.Attempt{
		Provider: constants.IntegrationProviderWeCom, Direction: constants.IntegrationDirectionOutbound,
		Operation: constants.IntegrationOperationWeComTemplateDetail, ResourceType: constants.WeComApprovalSceneResourceType,
		ResourceID: &resourceID, RequestSummary: map[string]any{
			"application_id": applicationID, "template_id": templateID,
		}, RequestID: requestID, CorrelationID: requestID,
	})
	if err != nil {
		return wecomapp.TemplateDefinition{}, err
	}
	startedAt := c.now()
	response, err := c.httpClient.Do(request)
	if err != nil {
		return wecomapp.TemplateDefinition{}, c.completeTemplateFailure(ctx, attempt.IntegrationID, 0, "request_failed", "企业微信模板详情请求失败", startedAt)
	}
	defer response.Body.Close()
	body, err := io.ReadAll(io.LimitReader(response.Body, constants.WeComDirectoryMaxResponseBodyBytes+1))
	if err != nil || int64(len(body)) > constants.WeComDirectoryMaxResponseBodyBytes {
		return wecomapp.TemplateDefinition{}, c.completeTemplateFailure(ctx, attempt.IntegrationID, response.StatusCode, "invalid_response", "企业微信模板详情响应无效", startedAt)
	}
	definition, errCode, errMsg, err := parseTemplateDefinition(templateID, body)
	if err != nil || response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices || errCode != 0 {
		providerCode := strconv.FormatInt(errCode, 10)
		if errCode == 0 {
			providerCode = strconv.Itoa(response.StatusCode)
		}
		return wecomapp.TemplateDefinition{}, c.completeTemplateFailure(ctx, attempt.IntegrationID, response.StatusCode, providerCode, errMsg, startedAt)
	}
	_, err = c.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
		Result: constants.IntegrationResultSuccess, HTTPStatus: response.StatusCode,
		ProviderCode: strconv.FormatInt(errCode, 10), ProviderMessage: errMsg,
		ResponseSummary: map[string]any{"errcode": errCode, "control_count": len(definition.Controls)},
		DurationMS:      c.now().Sub(startedAt).Milliseconds(),
	})
	if err != nil {
		return wecomapp.TemplateDefinition{}, err
	}
	return definition, nil
}

func (c *TemplateClient) newRequest(ctx context.Context, token, templateID string) (*http.Request, error) {
	endpoint, err := url.Parse(c.baseURL + "/cgi-bin/oa/gettemplatedetail")
	if err != nil || endpoint.Scheme == "" || endpoint.Host == "" {
		return nil, errors.New(errors.CodeWeComCredentialInvalid, "企业微信 API 地址配置无效")
	}
	query := endpoint.Query()
	query.Set("access_token", token)
	endpoint.RawQuery = query.Encode()
	body, err := sonic.Marshal(map[string]string{"template_id": templateID})
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "编码企业微信模板详情请求失败")
	}
	request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(body))
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "创建企业微信模板详情请求失败")
	}
	request.Header.Set("Content-Type", "application/json")
	return request, nil
}

func parseTemplateDefinition(templateID string, body []byte) (wecomapp.TemplateDefinition, int64, string, error) {
	var payload map[string]any
	if err := sonic.Unmarshal(body, &payload); err != nil {
		return wecomapp.TemplateDefinition{}, 0, "", err
	}
	errCode := numberToInt64(payload["errcode"])
	errMsg, _ := payload["errmsg"].(string)
	if errCode != 0 {
		return wecomapp.TemplateDefinition{}, errCode, errMsg, nil
	}
	content, ok := payload["template_content"].(map[string]any)
	if !ok {
		return wecomapp.TemplateDefinition{}, errCode, errMsg, errors.New(errors.CodeServiceUnavailable, "企业微信模板详情缺少模板内容")
	}
	controls := make([]wecomapp.TemplateControl, 0)
	seen := make(map[string]struct{})
	collectTemplateControls(content["controls"], &controls, seen)
	if len(controls) == 0 {
		return wecomapp.TemplateDefinition{}, errCode, errMsg, errors.New(errors.CodeInvalidParam, "企业微信模板没有可映射控件")
	}
	name := localizedText(content["template_name"])
	if name == "" {
		name = localizedText(content["template_names"])
	}
	return wecomapp.TemplateDefinition{
		TemplateID: templateID, Name: name, Controls: controls,
	}, errCode, errMsg, nil
}

func collectTemplateControls(value any, result *[]wecomapp.TemplateControl, seen map[string]struct{}) {
	switch typed := value.(type) {
	case []any:
		for _, item := range typed {
			collectTemplateControls(item, result, seen)
		}
	case map[string]any:
		if property, ok := typed["property"].(map[string]any); ok {
			id, _ := property["id"].(string)
			controlType, _ := property["control"].(string)
			if id != "" && controlType != "" {
				if _, exists := seen[id]; !exists {
					seen[id] = struct{}{}
					*result = append(*result, wecomapp.TemplateControl{
						ID: id, Type: controlType, Title: localizedText(property["title"]),
						Required: numberToInt64(property["require"]) == 1, OptionKeys: collectOptionKeys(typed),
					})
				}
			}
		}
		for _, item := range typed {
			collectTemplateControls(item, result, seen)
		}
	}
}

func collectOptionKeys(value any) []string {
	keys := make([]string, 0)
	seen := make(map[string]struct{})
	var walk func(any)
	walk = func(current any) {
		switch typed := current.(type) {
		case []any:
			for _, item := range typed {
				walk(item)
			}
		case map[string]any:
			if key, ok := typed["key"].(string); ok && key != "" {
				if _, exists := seen[key]; !exists {
					seen[key] = struct{}{}
					keys = append(keys, key)
				}
			}
			for _, item := range typed {
				walk(item)
			}
		}
	}
	walk(value)
	return keys
}

func localizedText(value any) string {
	if text, ok := value.(string); ok {
		return text
	}
	if items, ok := value.([]any); ok {
		for _, item := range items {
			if translated, ok := item.(map[string]any); ok {
				if text, ok := translated["text"].(string); ok && text != "" {
					return text
				}
			}
		}
	}
	return ""
}

func numberToInt64(value any) int64 {
	switch number := value.(type) {
	case float64:
		return int64(number)
	case int64:
		return number
	case int:
		return int64(number)
	default:
		return 0
	}
}

func (c *TemplateClient) completeTemplateFailure(ctx context.Context, integrationID string, status int, providerCode, providerMessage string, startedAt time.Time) error {
	if providerMessage == "" {
		providerMessage = "企业微信模板详情接口返回失败"
	}
	_, err := c.integration.Complete(ctx, integrationID, integrationlog.Completion{
		Result: constants.IntegrationResultFailed, HTTPStatus: status, ProviderCode: providerCode,
		ProviderMessage: providerMessage, ResponseSummary: map[string]any{"success": false},
		DurationMS: c.now().Sub(startedAt).Milliseconds(),
	})
	if err != nil {
		return err
	}
	return errors.New(errors.CodeInvalidParam, "企业微信审批模板无效或不可访问")
}
