package agent

import (
	"context"
	"fmt"
	"strings"
	"time"
)

func (bridge *AgentTeamMaaSLLMBridge) ensureMaaSProxyToken(ctx context.Context, forceRefresh bool) (string, error) {
	if bridge == nil || bridge.issuer == nil {
		return "", fmt.Errorf("agentteam llm bridge issuer is unavailable")
	}

	bridge.mu.Lock()
	defer bridge.mu.Unlock()

	now := time.Now().UTC()
	needsIssue := strings.TrimSpace(bridge.session.ProxyToken) == "" || strings.TrimSpace(bridge.session.SessionID) == ""
	needsRefresh := forceRefresh || sessionExpired(bridge.session, now) || sessionRefreshDue(bridge.session, now)

	var (
		session AgentTeamLLMProxySession
		err     error
	)
	switch {
	case needsIssue:
		selectorSessionID := strings.TrimSpace(bridge.session.SessionID)
		bridge.debugLog("agentteam maas llm bridge token issue started", "selector_session_id", selectorSessionID)
		session, err = bridge.issueMaaSProxySession(ctx, selectorSessionID)
		if err != nil && selectorSessionID != "" && !agentteamRuntimeSessionNotReady(err) {
			bridge.debugLog("agentteam maas llm bridge token issue retrying without selector", "selector_session_id", selectorSessionID, "error", err)
			session, err = bridge.issueMaaSProxySession(ctx, "")
		}
	case needsRefresh:
		selectorSessionID := strings.TrimSpace(bridge.session.SessionID)
		bridge.debugLog("agentteam maas llm bridge token refresh started", "selector_session_id", selectorSessionID, "force_refresh", forceRefresh)
		session, err = bridge.refreshMaaSProxySession(ctx, selectorSessionID)
		if err != nil && !agentteamRuntimeSessionNotReady(err) {
			bridge.debugLog("agentteam maas llm bridge token refresh falling back to issue", "selector_session_id", selectorSessionID, "error", err)
			session, err = bridge.issueMaaSProxySession(ctx, "")
		}
	default:
		bridge.debugLog("agentteam maas llm bridge token reused", "session_id", strings.TrimSpace(bridge.session.SessionID), "expires_at", bridge.session.ExpiresAt.Format(time.RFC3339))
		return strings.TrimSpace(bridge.session.ProxyToken), nil
	}
	if err != nil {
		bridge.debugLog("agentteam maas llm bridge token prepare failed", "force_refresh", forceRefresh, "error", err)
		return "", err
	}

	bridge.session = AgentTeamLLMProxySession{
		SessionID:      strings.TrimSpace(session.SessionID),
		ProxyToken:     strings.TrimSpace(session.ProxyToken),
		ExpiresAt:      session.ExpiresAt.UTC(),
		RefreshAt:      session.RefreshAt,
		MaasBaseURL:    strings.TrimSpace(session.MaasBaseURL),
		CredentialKind: strings.TrimSpace(session.CredentialKind),
	}
	bridge.debugLog("agentteam maas llm bridge token ready",
		"session_id", bridge.session.SessionID,
		"expires_at", bridge.session.ExpiresAt.Format(time.RFC3339),
		"refresh_at_set", bridge.session.RefreshAt != nil,
		"maas_base_url", sanitizeOpenCodeLogURL(bridge.session.MaasBaseURL),
		"credential_kind", bridge.session.CredentialKind,
	)
	return strings.TrimSpace(bridge.session.ProxyToken), nil
}

func (bridge *AgentTeamMaaSLLMBridge) issueMaaSProxySession(ctx context.Context, selectorSessionID string) (AgentTeamLLMProxySession, error) {
	return bridge.withRuntimeSessionPublishRetry(ctx, func() (AgentTeamLLMProxySession, error) {
		return bridge.issuer.IssueLLMProxySession(ctx, strings.TrimSpace(selectorSessionID))
	})
}

func (bridge *AgentTeamMaaSLLMBridge) refreshMaaSProxySession(ctx context.Context, selectorSessionID string) (AgentTeamLLMProxySession, error) {
	return bridge.withRuntimeSessionPublishRetry(ctx, func() (AgentTeamLLMProxySession, error) {
		return bridge.issuer.RefreshLLMProxySession(ctx, strings.TrimSpace(selectorSessionID))
	})
}

func (bridge *AgentTeamMaaSLLMBridge) withRuntimeSessionPublishRetry(ctx context.Context, issue func() (AgentTeamLLMProxySession, error)) (AgentTeamLLMProxySession, error) {
	if err := bridge.publishRuntimeSession(ctx); err != nil {
		return AgentTeamLLMProxySession{}, err
	}
	session, err := issue()
	if !agentteamRuntimeSessionNotReady(err) || !bridge.canRepublishRuntimeSession() {
		return session, err
	}
	if err := bridge.publishRuntimeSession(ctx); err != nil {
		return AgentTeamLLMProxySession{}, err
	}
	if err := bridge.waitRuntimeSessionReadyBackoff(ctx); err != nil {
		return AgentTeamLLMProxySession{}, err
	}
	return issue()
}

func (bridge *AgentTeamMaaSLLMBridge) publishRuntimeSession(ctx context.Context) error {
	if bridge == nil || bridge.runtimeSessionPublisher == nil {
		return nil
	}
	runtimeSessionID := strings.TrimSpace(bridge.runtimeSessionID)
	if runtimeSessionID == "" {
		return fmt.Errorf("agentteam runtime session id is required")
	}
	bridge.debugLog("agentteam maas llm bridge runtime session publish", "runtime_session_id", runtimeSessionID)
	return bridge.runtimeSessionPublisher(ctx, runtimeSessionID)
}

func (bridge *AgentTeamMaaSLLMBridge) canRepublishRuntimeSession() bool {
	return bridge != nil && bridge.runtimeSessionPublisher != nil && strings.TrimSpace(bridge.runtimeSessionID) != ""
}

func (bridge *AgentTeamMaaSLLMBridge) waitRuntimeSessionReadyBackoff(ctx context.Context) error {
	if bridge != nil && bridge.runtimeSessionNotReadyBackoff != nil {
		return bridge.runtimeSessionNotReadyBackoff(ctx)
	}
	if ctx == nil {
		ctx = context.Background()
	}
	timer := time.NewTimer(agentteamLLMBridgeRuntimeReadyBackoff)
	defer timer.Stop()
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-timer.C:
		return nil
	}
}

func agentteamRuntimeSessionNotReady(err error) bool {
	if err == nil {
		return false
	}
	message := strings.ToLower(err.Error())
	return strings.Contains(message, agentteamRuntimeSessionNotReadyCode) ||
		strings.Contains(message, agentteamRuntimeSessionNotReadyMessage)
}

func sessionExpired(session AgentTeamLLMProxySession, now time.Time) bool {
	if strings.TrimSpace(session.ProxyToken) == "" {
		return true
	}
	return !session.ExpiresAt.After(now.Add(agentteamLLMBridgeSessionExpirySkew))
}

func sessionRefreshDue(session AgentTeamLLMProxySession, now time.Time) bool {
	if session.RefreshAt == nil {
		return false
	}
	return !session.RefreshAt.UTC().After(now)
}
