// Package copilot implements the GitHub Copilot LLM provider for the gateway system.
// This file contains the main Copilot struct and methods for interacting with
// GitHub's Copilot API, including chat completions, embeddings, and model queries.
package copilot

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/apr/provider/copilot/state"
	"github.com/tidbcloud/AgentTeam/internal/apr/types"
)

const (
	// CopilotAPIBase is the base URL for GitHub Copilot API endpoints.
	// All API calls to GitHub Copilot services use this base URL.
	CopilotAPIBase = "https://api.githubcopilot.com"
)

// Copilot implements the types.LLM interface for GitHub Copilot integration.
// It provides methods for chat completions, embeddings, and model information
// by communicating with GitHub's Copilot API using proper authentication and headers.
type Copilot struct {
	// transporter handles HTTP transport configuration (proxy, etc.)
	transporter types.HTTPProxy
	// httpClient is a shared HTTP client for connection reuse across requests
	httpClient *http.Client
	// modelCache manages the model caching using CopilotModelProvider
	modelCache *types.ModelCache
	// cancelAuth stops background token refresh work owned by this provider.
	cancelAuth context.CancelFunc
}

// client returns the shared HTTP client, creating one on first access.
func (c *Copilot) client() *http.Client {
	if c.httpClient != nil {
		return c.httpClient
	}
	if c.transporter != nil {
		c.httpClient = &http.Client{Transport: c.transporter.Transport()}
	} else {
		c.httpClient = &http.Client{}
	}
	return c.httpClient
}

// Models returns the cached list of available models from GitHub Copilot.
// The models are cached using the ModelCache to avoid unnecessary API calls and provide
// fast access to model information for client requests.
//
// Returns:
//   - []types.Model: The cached models from the model cache
func (c *Copilot) Models() []types.Model {
	return c.modelCache.Models()
}

// Name returns the canonical name of the Copilot provider.
// This matches the registration name used when registering with the dispatcher.
//
// Returns:
//   - string: The canonical name "copilot"
func (c *Copilot) Name() string {
	return "copilot"
}

// Close stops background work owned by the provider.
func (c *Copilot) Close() error {
	if c == nil {
		return nil
	}
	if c.cancelAuth != nil {
		c.cancelAuth()
	}
	if c.modelCache != nil {
		return c.modelCache.Close()
	}
	return nil
}

// ChatCompletion sends a chat completion request to GitHub Copilot API.
// This method implements the OpenAICompatible interface and handles the complete
// flow of processing chat requests, including:
//   - Vision content detection for multimodal requests
//   - Agent call detection for tool-assisted conversations
//   - Proper header construction with authentication and metadata
//   - HTTP request execution and response handling
//
// The function automatically detects special request types:
//   - Vision requests: Detected by the presence of image_url content types
//   - Agent calls: Detected by assistant or tool role messages
//
// Parameters:
//   - headers: The HTTP headers from the original request (used for forwarding)
//   - jsonData: The raw request body bytes
//
// Returns:
//   - *http.Response: The HTTP response from the Copilot API
//   - error: Any error that occurred during request processing
func (c *Copilot) ChatCompletion(ctx context.Context, headers http.Header, jsonData []byte, opts ...types.CompletionOption) (*http.Response, error) {
	// Parse completion options
	var options types.CompletionOptions
	for _, opt := range opts {
		opt(&options)
	}

	// Get available token from manager, using model-aware selection when model is provided
	var tokenState *state.TokenState
	if options.Model != "" {
		tokenState = state.Manager.AvailableTokenForModel(options.Model)
		if tokenState == nil {
			return nil, fmt.Errorf("no available token for model %s (requires %s tier)", options.Model, state.TierForModel(options.Model))
		}
	} else {
		tokenState = state.Manager.AvailableToken()
		if tokenState == nil {
			return nil, fmt.Errorf("no available token")
		}
	}

	token := tokenState.CopilotToken()
	if token == "" {
		return nil, fmt.Errorf("token not found")
	}

	// Update last request time for this token
	tokenState.SetLastRequestTime(time.Now())

	// Parse only the messages field for vision and agent detection
	var req struct {
		Messages []types.Message `json:"messages"`
	}
	if err := json.Unmarshal(jsonData, &req); err != nil {
		return nil, fmt.Errorf("failed to parse request: %w", err)
	}

	// Analyze request content to detect vision capabilities needed
	enableVision := false
	for _, msg := range req.Messages {
		if contentArray, ok := msg.Content.([]any); ok {
			for _, content := range contentArray {
				if contentMap, ok := content.(map[string]any); ok {
					if contentMap["type"] == "image_url" {
						enableVision = true
						break
					}
				}
			}
		}
		if enableVision {
			break
		}
	}

	// Determine if this is an agent-assisted conversation
	// Agent calls have different header requirements and behavior
	isAgentCall := false
	for _, msg := range req.Messages {
		if msg.Role == "assistant" || msg.Role == "tool" {
			isAgentCall = true
			break
		}
	}

	// Build appropriate headers for the request type
	copilotHeaders := buildCopilotHeaders(token, enableVision, isAgentCall)

	// Create and configure the HTTP request
	httpReq, err := http.NewRequestWithContext(ctx, "POST", CopilotAPIBase+"/chat/completions", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}

	// Apply all required headers
	for key, value := range copilotHeaders {
		httpReq.Header.Set(key, value)
	}

	// Execute the HTTP request
	return c.client().Do(httpReq)
}

// CreateEmbedding sends an embedding request to GitHub Copilot API.
// This method converts input text into vector embeddings using Copilot's
// embedding models. The embeddings can be used for semantic search,
// similarity calculations, and other NLP tasks.
//
// Parameters:
//   - req: The embedding request containing model and input text
//
// Returns:
//   - *types.EmbeddingResponse: The embedding response with vector data
//   - error: Any error that occurred during request processing
func (c *Copilot) CreateEmbedding(ctx context.Context, req *types.EmbeddingRequest) (*types.EmbeddingResponse, error) {
	// Get available token from manager
	tokenState := state.Manager.AvailableToken()
	if tokenState == nil {
		return nil, fmt.Errorf("no available token")
	}

	token := tokenState.CopilotToken()
	if token == "" {
		return nil, fmt.Errorf("token not found")
	}

	// Update last request time for this token
	tokenState.SetLastRequestTime(time.Now())

	// Build headers (embeddings don't require vision or agent flags)
	headers := buildCopilotHeaders(token, false, false)

	// Serialize the request to JSON
	jsonData, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	// Create and configure the HTTP request
	httpReq, err := http.NewRequestWithContext(ctx, "POST", CopilotAPIBase+"/embeddings", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}

	// Apply all required headers
	for key, value := range headers {
		httpReq.Header.Set(key, value)
	}

	// Execute the HTTP request
	resp, err := c.client().Do(httpReq)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Handle non-success responses
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("copilot API error: %s", resp.Status)
	}

	// Parse the JSON response
	var embeddingResp types.EmbeddingResponse
	if err := json.NewDecoder(resp.Body).Decode(&embeddingResp); err != nil {
		return nil, err
	}

	return &embeddingResp, nil
}

// buildCopilotHeaders constructs the required headers for GitHub Copilot API requests.
// This function creates a comprehensive set of headers that simulate a VS Code client
// with the Copilot extension, ensuring proper authentication and compatibility.
//
// The headers include:
//   - Authentication: Bearer token for API access
//   - Client identification: User agent, editor versions, session IDs
//   - Request metadata: Request ID, machine ID, integration details
//   - Feature flags: Vision and agent call indicators
//
// Parameters:
//   - token: The Copilot authentication token
//   - enableVision: Whether to enable vision/multimodal capabilities
//   - isAgentCall: Whether this is an agent-assisted call
//
// Returns:
//   - map[string]string: Complete set of headers for the API request
func buildCopilotHeaders(token string, enableVision bool, isAgentCall bool) map[string]string {
	headers := map[string]string{
		// Authentication and content type
		"Authorization": fmt.Sprintf("Bearer %s", token),
		"Content-Type":  "application/json",

		// Client identification (simulates VS Code with Copilot extension)
		"User-Agent":            "GitHubCopilotChat/" + state.Manager.CopilotChatVersion(),
		"Editor-Version":        "vscode/1=" + state.Manager.VSCodeVersion(),
		"Editor-Plugin-Version": "copilot-chat/" + state.Manager.CopilotChatVersion(),

		// HTTP headers for proper content handling
		"Accept":          "*/*",
		"Accept-Encoding": "gzip, deflate, br",

		// Request tracking and session management
		"X-Request-Id":     generateRequestID(),
		"Vscode-Sessionid": generateSessionID(),
		"Vscode-Machineid": generateMachineID(),

		// OpenAI and Copilot specific metadata
		"Openai-Organization":    "github-copilot",
		"Openai-Intent":          "conversation-panel",
		"Copilot-Integration-Id": "vscode-chat",
	}

	// Modify headers based on request type
	if enableVision {
		// Vision requests use a different intent for multimodal processing
		headers["Openai-Intent"] = "copilot-ghost"
	}

	// Indicate whether this is an agent-assisted conversation
	if isAgentCall {
		headers["X-Initiator"] = "agent"
	} else {
		headers["X-Initiator"] = "user"
	}

	return headers
}

// generateRequestID creates a unique request identifier for tracking API calls.
// Each request gets a unique ID that can be used for debugging, logging,
// and correlating requests across the system.
//
// Returns:
//   - string: A unique request ID with "req_" prefix
func generateRequestID() string {
	return "req_" + generateRandomString(32)
}

// generateSessionID creates a unique session identifier for VS Code simulation.
// This simulates a persistent VS Code session and helps maintain consistency
// across multiple requests within the same logical session.
//
// Returns:
//   - string: A unique session ID with "sess_" prefix
func generateSessionID() string {
	return "sess_" + generateRandomString(32)
}

// generateMachineID creates a unique machine identifier for client identification.
// This simulates a VS Code installation on a specific machine and helps
// GitHub Copilot maintain user context and preferences.
//
// Returns:
//   - string: A unique machine identifier
func generateMachineID() string {
	return generateRandomString(64)
}

// generateRandomString creates a random string of specified length using alphanumeric characters.
// This utility function is used for generating unique identifiers that simulate
// VS Code client behavior. The generated strings use lowercase letters and numbers
// to match the expected format of VS Code identifiers.
//
// Parameters:
//   - length: The desired length of the random string
//
// Returns:
//   - string: A random string of the specified length
func generateRandomString(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
	result := make([]byte, length)
	for i := range result {
		// Simple deterministic character selection for consistent behavior
		// In production, this could be replaced with crypto/rand for true randomness
		result[i] = charset[i%len(charset)]
	}
	return string(result)
}
