package providers

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/google/uuid"
)

const (
	iflowCompatBaseURL   = "https://apis.iflow.cn/v1"
	iflowCompatEndpoint  = "/chat/completions"
	iflowCompatUserAgent = "iFlow-Cli"
)

type IFlowProvider struct {
	base *HTTPProvider
}

func NewIFlowProvider(providerName, apiKey, apiBase, defaultModel string, supportsResponsesCompact bool, authMode string, timeout time.Duration, oauth *oauthManager) *IFlowProvider {
	return &IFlowProvider{base: NewHTTPProvider(providerName, apiKey, apiBase, defaultModel, supportsResponsesCompact, authMode, timeout, oauth)}
}

func (p *IFlowProvider) GetDefaultModel() string { return openAICompatDefaultModel(p.base) }

func (p *IFlowProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) {
	if p == nil || p.base == nil {
		return nil, fmt.Errorf("provider not configured")
	}
	body := buildIFlowChatRequest(p.base, messages, tools, model, options, false)
	respBody, statusCode, contentType, err := doIFlowJSONWithAttempts(ctx, p.base, body)
	if err != nil {
		return nil, err
	}
	if statusCode != http.StatusOK {
		return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(respBody))
	}
	if !json.Valid(respBody) {
		return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(respBody))
	}
	return parseOpenAICompatResponse(respBody)
}

func (p *IFlowProvider) ChatStream(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}, onDelta func(string)) (*LLMResponse, error) {
	if p == nil || p.base == nil {
		return nil, fmt.Errorf("provider not configured")
	}
	if onDelta == nil {
		onDelta = func(string) {}
	}
	body := buildIFlowChatRequest(p.base, messages, tools, model, options, true)
	respBody, statusCode, contentType, err := doIFlowStreamWithAttempts(ctx, p.base, body, onDelta)
	if err != nil {
		return nil, err
	}
	if statusCode != http.StatusOK {
		return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(respBody))
	}
	if !json.Valid(respBody) {
		return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(respBody))
	}
	return parseOpenAICompatResponse(respBody)
}

func (p *IFlowProvider) CountTokens(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*UsageInfo, error) {
	if p == nil || p.base == nil {
		return nil, fmt.Errorf("provider not configured")
	}
	body := buildIFlowChatRequest(p.base, messages, tools, model, options, false)
	count, err := estimateOpenAICompatTokenCount(body)
	if err != nil {
		return nil, err
	}
	return &UsageInfo{
		PromptTokens: count,
		TotalTokens:  count,
	}, nil
}

func buildIFlowChatRequest(base *HTTPProvider, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}, stream bool) map[string]interface{} {
	baseModel := qwenBaseModel(model)
	body := base.buildOpenAICompatChatRequest(messages, tools, baseModel, options)
	if stream {
		body["stream"] = true
		body["stream_options"] = map[string]interface{}{"include_usage": true}
		iflowEnsureToolsArray(body)
	}
	applyIFlowThinking(body, model)
	return body
}

func applyIFlowThinking(body map[string]interface{}, model string) {
	enabled, ok := iflowThinkingEnabled(model, body)
	if !ok {
		return
	}
	lowerModel := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", body["model"])))
	if strings.HasPrefix(lowerModel, "minimax") {
		body["reasoning_split"] = enabled
		return
	}
	kwargs, _ := body["chat_template_kwargs"].(map[string]interface{})
	if kwargs == nil {
		kwargs = map[string]interface{}{}
	}
	kwargs["enable_thinking"] = enabled
	delete(kwargs, "clear_thinking")
	if enabled && strings.HasPrefix(lowerModel, "glm") {
		kwargs["clear_thinking"] = false
	}
	body["chat_template_kwargs"] = kwargs
}

func iflowThinkingEnabled(model string, body map[string]interface{}) (bool, bool) {
	if suffix := strings.ToLower(strings.TrimSpace(qwenModelSuffix(model))); suffix != "" {
		switch suffix {
		case "none":
			return false, true
		default:
			return true, true
		}
	}
	if effort := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", body["reasoning_effort"]))); effort != "" {
		return effort != "none", true
	}
	if thinking, ok := body["thinking"].(map[string]interface{}); ok {
		typ := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", thinking["type"])))
		if typ == "disabled" {
			return false, true
		}
		if budget, ok := thinking["budget_tokens"]; ok {
			return intValue(budget) > 0, true
		}
		if typ != "" {
			return true, true
		}
	}
	return false, false
}

func iflowEnsureToolsArray(body map[string]interface{}) {
	if _, exists := body["tools"]; !exists {
		body["tools"] = []map[string]interface{}{}
	}
	switch tools := body["tools"].(type) {
	case []map[string]interface{}:
		if len(tools) > 0 {
			return
		}
	case []interface{}:
		if len(tools) > 0 {
			return
		}
	default:
		return
	}
	body["tools"] = []map[string]interface{}{
		{
			"type": "function",
			"function": map[string]interface{}{
				"name":        "noop",
				"description": "Placeholder tool to stabilise streaming",
				"parameters": map[string]interface{}{
					"type": "object",
				},
			},
		},
	}
}

func doIFlowJSONWithAttempts(ctx context.Context, base *HTTPProvider, payload map[string]interface{}) ([]byte, int, string, error) {
	if base == nil {
		return nil, 0, "", fmt.Errorf("provider not configured")
	}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return nil, 0, "", fmt.Errorf("failed to marshal request: %w", err)
	}
	attempts, err := base.authAttempts(ctx)
	if err != nil {
		return nil, 0, "", err
	}
	var lastBody []byte
	var lastStatus int
	var lastType string
	for _, attempt := range attempts {
		req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpointFor(iflowBaseURLForAttempt(base, attempt), iflowCompatEndpoint), bytes.NewReader(jsonData))
		if err != nil {
			return nil, 0, "", fmt.Errorf("failed to create request: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "application/json")
		applyAttemptAuth(req, attempt)
		applyIFlowHeaders(req, iflowAttemptAPIKey(attempt), false)

		body, status, contentType, err := base.doJSONAttempt(req, attempt)
		if err != nil {
			return nil, 0, "", err
		}
		reason, retry := classifyOAuthFailure(status, body)
		if !retry {
			base.markAttemptSuccess(attempt)
			return body, status, contentType, nil
		}
		lastBody, lastStatus, lastType = body, status, contentType
		applyAttemptFailure(base, attempt, reason, nil)
	}
	return lastBody, lastStatus, lastType, nil
}

func doIFlowStreamWithAttempts(ctx context.Context, base *HTTPProvider, payload map[string]interface{}, onDelta func(string)) ([]byte, int, string, error) {
	if base == nil {
		return nil, 0, "", fmt.Errorf("provider not configured")
	}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return nil, 0, "", fmt.Errorf("failed to marshal request: %w", err)
	}
	attempts, err := base.authAttempts(ctx)
	if err != nil {
		return nil, 0, "", err
	}
	var lastBody []byte
	var lastStatus int
	var lastType string
	for _, attempt := range attempts {
		req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpointFor(iflowBaseURLForAttempt(base, attempt), iflowCompatEndpoint), bytes.NewReader(jsonData))
		if err != nil {
			return nil, 0, "", fmt.Errorf("failed to create request: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Accept", "text/event-stream")
		applyAttemptAuth(req, attempt)
		applyIFlowHeaders(req, iflowAttemptAPIKey(attempt), true)

		body, status, contentType, quotaHit, err := base.doStreamAttempt(req, attempt, func(event string) {
			var obj map[string]interface{}
			if err := json.Unmarshal([]byte(event), &obj); err != nil {
				return
			}
			choices, _ := obj["choices"].([]interface{})
			for _, choice := range choices {
				item, _ := choice.(map[string]interface{})
				delta, _ := item["delta"].(map[string]interface{})
				if txt := strings.TrimSpace(fmt.Sprintf("%v", delta["content"])); txt != "" {
					onDelta(txt)
				}
			}
		})
		if err != nil {
			return nil, 0, "", err
		}
		if !quotaHit {
			base.markAttemptSuccess(attempt)
			return body, status, contentType, nil
		}
		lastBody, lastStatus, lastType = body, status, contentType
		reason, _ := classifyOAuthFailure(status, body)
		applyAttemptFailure(base, attempt, reason, nil)
	}
	return lastBody, lastStatus, lastType, nil
}

func iflowBaseURLForAttempt(base *HTTPProvider, attempt authAttempt) string {
	if attempt.session != nil {
		if raw := strings.TrimSpace(attempt.session.ResourceURL); raw != "" {
			return normalizeIFlowBaseURL(raw)
		}
		if attempt.session.Token != nil {
			if raw := firstNonEmpty(
				strings.TrimSpace(asString(attempt.session.Token["base_url"])),
				strings.TrimSpace(asString(attempt.session.Token["base-url"])),
			); raw != "" {
				return normalizeIFlowBaseURL(raw)
			}
			if raw := firstNonEmpty(
				strings.TrimSpace(asString(attempt.session.Token["resource_url"])),
				strings.TrimSpace(asString(attempt.session.Token["resource-url"])),
			); raw != "" {
				return normalizeIFlowBaseURL(raw)
			}
		}
	}
	if base != nil && strings.TrimSpace(base.apiBase) != "" && !strings.Contains(strings.ToLower(base.apiBase), "api.openai.com") {
		return normalizeIFlowBaseURL(base.apiBase)
	}
	return iflowCompatBaseURL
}

func normalizeIFlowBaseURL(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return iflowCompatBaseURL
	}
	if !strings.Contains(trimmed, "://") {
		trimmed = "https://" + trimmed
	}
	trimmed = normalizeAPIBase(trimmed)
	if strings.HasSuffix(strings.ToLower(trimmed), "/chat/completions") {
		trimmed = strings.TrimSuffix(trimmed, "/chat/completions")
	}
	if !strings.HasSuffix(strings.ToLower(trimmed), "/v1") {
		trimmed = strings.TrimRight(trimmed, "/") + "/v1"
	}
	return trimmed
}

func iflowAttemptAPIKey(attempt authAttempt) string {
	if attempt.session != nil && attempt.session.Token != nil {
		if v := firstNonEmpty(
			strings.TrimSpace(asString(attempt.session.Token["api_key"])),
			strings.TrimSpace(asString(attempt.session.Token["api-key"])),
			strings.TrimSpace(asString(attempt.session.Token["apiKey"])),
		); v != "" {
			return v
		}
	}
	return strings.TrimSpace(attempt.token)
}

func applyIFlowHeaders(req *http.Request, apiKey string, stream bool) {
	if req == nil {
		return
	}
	if strings.TrimSpace(apiKey) != "" {
		req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(apiKey))
	}
	req.Header.Set("User-Agent", iflowCompatUserAgent)
	sessionID := "session-" + uuid.New().String()
	req.Header.Set("session-id", sessionID)
	timestamp := time.Now().UnixMilli()
	req.Header.Set("x-iflow-timestamp", fmt.Sprintf("%d", timestamp))
	if sig := createIFlowSignature(iflowCompatUserAgent, sessionID, timestamp, apiKey); sig != "" {
		req.Header.Set("x-iflow-signature", sig)
	}
	if stream {
		req.Header.Set("Accept", "text/event-stream")
	} else {
		req.Header.Set("Accept", "application/json")
	}
}

func createIFlowSignature(userAgent, sessionID string, timestamp int64, apiKey string) string {
	if strings.TrimSpace(apiKey) == "" {
		return ""
	}
	payload := fmt.Sprintf("%s:%s:%d", userAgent, sessionID, timestamp)
	h := hmac.New(sha256.New, []byte(apiKey))
	_, _ = h.Write([]byte(payload))
	return hex.EncodeToString(h.Sum(nil))
}
