package client

import (
	"context"
	"encoding/json"
	"fmt"
	"iter"
	"net/http"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
	"google.golang.org/genai"

	"github.com/tingly-dev/tingly-box/ai"
	"github.com/tingly-dev/tingly-box/internal/obs"
	"github.com/tingly-dev/tingly-box/internal/protocol"
	"github.com/tingly-dev/tingly-box/internal/typ"
)

// GoogleClient wraps the Google genai SDK client
type GoogleClient struct {
	client     *genai.Client
	provider   *typ.Provider
	debugMode  bool
	httpClient *http.Client
	recordSink *obs.Sink
}

// NewGoogleClient creates a new Google client wrapper.
// sessionID is used for session-scoped transport creation for OAuth providers.
//
// For provider-specific wire formats (Gemini CLI / Antigravity Code Assist
// envelope), prefer the dedicated *Client constructors (NewGeminiClient,
// NewAntigravityClient) — they layer the right round tripper on top of the
// session-bound transport. NewGoogleClient itself stays generic.
func NewGoogleClient(provider *typ.Provider, model string, sessionID typ.SessionID) (*GoogleClient, error) {
	var transport http.RoundTripper
	if provider.AuthType == typ.AuthTypeOAuth || provider.ProxyURL != "" {
		transport = createSessionBoundTransport(provider, sessionID)

		issuer := ""
		if provider.OAuthDetail != nil {
			issuer = string(provider.OAuthDetail.Issuer)
		}
		if issuer != "" {
			logrus.Infof("Using session-bound transport for OAuth provider type: %s, session: %s", issuer, sessionID.Value)
		}
		if provider.ProxyURL != "" {
			logrus.Infof("Using proxy for Google client: %s", provider.ProxyURL)
		}
	} else {
		// Use the transport pool instead of http.DefaultTransport so that env
		// proxy variables (HTTP_PROXY / HTTPS_PROXY) are not inherited when no
		// proxy is explicitly configured for the provider.
		transport = GetGlobalTransportPool().GetTransport(provider.UUID, model, provider.ProxyURL, ai.Issuer(""), sessionID)
	}

	httpClient := &http.Client{Transport: transport}
	return newGoogleClientFromHTTPClient(provider, httpClient)
}

// newGoogleClientFromHTTPClient builds a GoogleClient around an already-configured
// http.Client. Provider-specific clients (Gemini, Antigravity) use this to
// inject their round tripper without going through the generic transport path.
func newGoogleClientFromHTTPClient(provider *typ.Provider, httpClient *http.Client) (*GoogleClient, error) {
	httpOptions := genai.HTTPOptions{
		BaseURL: provider.APIBase,
	}

	config := &genai.ClientConfig{
		APIKey:      provider.GetAccessToken(),
		HTTPOptions: httpOptions,
		HTTPClient:  httpClient,
	}

	ctx := context.Background()
	client, err := genai.NewClient(ctx, config)
	if err != nil {
		return nil, err
	}

	return &GoogleClient{
		client:     client,
		provider:   provider,
		httpClient: httpClient,
	}, nil
}

// ProviderType returns the provider type
func (c *GoogleClient) APIStyle() protocol.APIStyle {
	return protocol.APIStyleGoogle
}

// Close closes any resources held by the client
func (c *GoogleClient) Close() error {
	if c.httpClient != nil && c.httpClient != http.DefaultClient {
		c.httpClient.CloseIdleConnections()
	}
	return nil
}

// Client returns the underlying Google genai SDK client
func (c *GoogleClient) Client() *genai.Client {
	return c.client
}

// GenerateContent generates content using the Google API
func (c *GoogleClient) GenerateContent(ctx context.Context, model string, contents []*genai.Content, config *genai.GenerateContentConfig) (*genai.GenerateContentResponse, error) {
	return c.client.Models.GenerateContent(ctx, model, contents, config)
}

// GenerateContentStream generates content using streaming
func (c *GoogleClient) GenerateContentStream(ctx context.Context, model string, contents []*genai.Content, config *genai.GenerateContentConfig) iter.Seq2[*genai.GenerateContentResponse, error] {
	return c.client.Models.GenerateContentStream(ctx, model, contents, config)
}

// SetRecordSink sets the record sink for the client
func (c *GoogleClient) SetRecordSink(sink *obs.Sink) {
	c.recordSink = sink
	if sink != nil && sink.IsEnabled() {
		c.applyRecordMode()
	}
}

// applyRecordMode wraps the HTTP client with a record round tripper
func (c *GoogleClient) applyRecordMode() {
	if c.recordSink == nil {
		return
	}
	c.httpClient.Transport = NewRecordRoundTripper(c.httpClient.Transport, c.recordSink, c.provider)
}

// GetProvider returns the provider for this client
func (c *GoogleClient) GetProvider() *typ.Provider {
	return c.provider
}

// ListModels returns the list of available models from the Google Gemini API
// Note: Google genai SDK doesn't have a direct ListModels method, so we return
// ErrModelsEndpointNotSupported to signal the caller to use template fallback.
func (c *GoogleClient) ListModels(ctx context.Context) ([]string, error) {
	// Google genai SDK doesn't provide a models list endpoint
	// The caller should use template fallback instead
	return nil, &ErrModelsEndpointNotSupported{
		Provider: c.provider.Name,
		Reason:   "Google genai SDK does not support listing models via API",
	}
}

// ProbeChatEndpoint tests the chat endpoint with a minimal request
func (c *GoogleClient) Probe(ctx context.Context, model string) ProbeResult {
	startTime := time.Now()

	// Create minimal content for probe
	contents := []*genai.Content{
		{
			Role: "user",
			Parts: []*genai.Part{
				{Text: "hi"},
			},
		},
	}

	// Configure generation with minimal tokens
	config := &genai.GenerateContentConfig{
		MaxOutputTokens: 1000,
	}

	// Make request
	resp, err := c.client.Models.GenerateContent(ctx, model, contents, config)
	latencyMs := time.Since(startTime).Milliseconds()

	if err != nil {
		return ProbeResult{
			Success:      false,
			ErrorMessage: err.Error(),
			LatencyMs:    latencyMs,
		}
	}

	// Extract response data
	responseContent := ""
	promptTokens := 0
	completionTokens := 0
	totalTokens := 0

	if resp != nil && len(resp.Candidates) > 0 {
		candidate := resp.Candidates[0]
		if candidate.Content != nil {
			for _, part := range candidate.Content.Parts {
				if part.Text != "" {
					responseContent += part.Text
				}
			}
		}
		if resp.UsageMetadata != nil {
			promptTokens = int(resp.UsageMetadata.PromptTokenCount)
			completionTokens = int(resp.UsageMetadata.CandidatesTokenCount)
			totalTokens = int(resp.UsageMetadata.TotalTokenCount)
		}
	}

	if responseContent == "" {
		responseContent = "<response content is empty, but request success>"
	}

	return ProbeResult{
		Success:          true,
		Message:          "Chat endpoint is accessible",
		Content:          responseContent,
		LatencyMs:        latencyMs,
		PromptTokens:     promptTokens,
		CompletionTokens: completionTokens,
		TotalTokens:      totalTokens,
	}
}

// probeStream performs a streaming probe with configurable test mode
// Note: Google genai SDK has limited tool support in probe context

// ProbeStream performs a streaming probe with configurable test mode (public interface)
func (c *GoogleClient) ProbeStream(ctx context.Context, model, message string, testMode ProbeMode) (*ProbeResult, error) {
	return c.probeStream(ctx, model, message, testMode)
}
func (c *GoogleClient) probeStream(ctx context.Context, model, message string, testMode ProbeMode) (*ProbeResult, error) {
	startTime := time.Now()

	// Create minimal content for probe
	contents := []*genai.Content{
		{
			Role: "user",
			Parts: []*genai.Part{
				{Text: message},
			},
		},
	}

	// Configure generation
	config := &genai.GenerateContentConfig{
		MaxOutputTokens: 1024,
	}

	// For simple mode, use non-streaming request
	if testMode == ProbeModeSimple {
		resp, err := c.client.Models.GenerateContent(ctx, model, contents, config)
		if err != nil {
			return nil, err
		}

		respJSON, _ := json.Marshal(resp)
		return ToProbeResult(string(respJSON), time.Since(startTime).Milliseconds(), c.provider.APIBase, false), nil
	}

	// For streaming and tool modes, use streaming
	stream := c.client.Models.GenerateContentStream(ctx, model, contents, config)

	var chunks []interface{}
	for resp, err := range stream {
		if err != nil {
			return nil, err
		}
		chunks = append(chunks, resp)
	}

	chunksJSON, _ := json.Marshal(chunks)
	return ToProbeResult(string(chunksJSON), time.Since(startTime).Milliseconds(), c.provider.APIBase, true), nil
}

// ProbeModelsEndpoint tests the models list endpoint
func (c *GoogleClient) ProbeModelsEndpoint(ctx context.Context) ProbeResult {
	// Google genai SDK doesn't provide a models list endpoint
	// Return an error result indicating this is not supported
	return ProbeResult{
		Success:      false,
		ErrorMessage: "Google genai SDK does not support listing models via API",
		LatencyMs:    0,
	}
}

// ProbeOptionsEndpoint tests basic connectivity with an OPTIONS request
func (c *GoogleClient) ProbeOptionsEndpoint(ctx context.Context) ProbeResult {
	startTime := time.Now()

	// Use the API base URL for OPTIONS request
	optionsURL := c.provider.APIBase
	if !strings.HasSuffix(optionsURL, "/") {
		optionsURL += "/"
	}

	req, err := http.NewRequestWithContext(ctx, "OPTIONS", optionsURL, nil)
	if err != nil {
		return ProbeResult{
			Success:      false,
			ErrorMessage: fmt.Sprintf("Failed to create OPTIONS request: %v", err),
		}
	}

	// Set authentication header
	req.Header.Set("x-goog-api-key", c.provider.GetAccessToken())

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Do(req)
	latencyMs := time.Since(startTime).Milliseconds()

	if err != nil {
		return ProbeResult{
			Success:      false,
			ErrorMessage: fmt.Sprintf("OPTIONS request failed: %v", err),
			LatencyMs:    latencyMs,
		}
	}
	defer resp.Body.Close()

	// Consider any 2xx status as success for OPTIONS
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return ProbeResult{
			Success:   true,
			Message:   "OPTIONS request successful",
			LatencyMs: latencyMs,
		}
	}

	return ProbeResult{
		Success:      false,
		ErrorMessage: fmt.Sprintf("OPTIONS request failed with status: %d", resp.StatusCode),
		LatencyMs:    latencyMs,
	}
}
