// Package copilot provides a model provider implementation for GitHub Copilot.
// This file implements the ModelProvider interface to fetch models from Copilot API.
package copilot

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"time"

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

// ModelProvider implements the ModelProvider interface for GitHub Copilot.
type ModelProvider struct {
	// staticModels is the fallback list used when no token is available.
	staticModels []types.StaticModel
	httpClient   *http.Client
}

// NewModelProvider creates a new Copilot model provider with the given static model fallback.
func NewModelProvider(staticModels []types.StaticModel, httpClient ...*http.Client) *ModelProvider {
	var client *http.Client
	if len(httpClient) > 0 {
		client = httpClient[0]
	}
	return &ModelProvider{staticModels: staticModels, httpClient: client}
}

// FetchModels retrieves the list of available models from GitHub Copilot API.
// If no token is available, it falls back to the static model list.
func (cmp *ModelProvider) FetchModels(ctx context.Context) ([]types.Model, error) {
	tokenState := state.Manager.AnyToken()
	if tokenState == nil {
		return cmp.fetchStaticModels()
	}

	// Get the Copilot token from the available token state
	copilotToken := tokenState.CopilotToken()
	if copilotToken == "" {
		return cmp.fetchStaticModels()
	}

	slog.Info("Fetching models from GitHub Copilot API")

	// Build headers for the models request
	headers := buildCopilotHeaders(copilotToken, false, false)

	// Create and configure the HTTP request
	httpReq, err := http.NewRequestWithContext(ctx, "GET", CopilotAPIBase+"/models", nil)
	if err != nil {
		return nil, err
	}

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

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

	// Handle non-success responses
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		slog.Error("Copilot API error", "status", resp.Status, "body", string(body))
		return nil, fmt.Errorf("copilot API error: %s", resp.Status)
	}

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

	return cmp.mergeWithStatic(modelsResp.Data), nil
}

// mergeWithStatic ensures all static models are present in the result.
// Dynamic models from the API are kept; static models not already included are appended.
func (cmp *ModelProvider) mergeWithStatic(dynamic []types.Model) []types.Model {
	if len(cmp.staticModels) == 0 {
		return dynamic
	}

	seen := make(map[string]bool, len(dynamic))
	for _, m := range dynamic {
		seen[m.ID] = true
	}

	now := time.Now().Unix()
	for _, sm := range cmp.staticModels {
		if !seen[sm.ID] {
			dynamic = append(dynamic, types.Model{
				ID:      sm.ID,
				Object:  "model",
				Created: now,
				OwnedBy: "copilot",
				Capabilities: types.Capabilities{
					Type: "chat",
					Limits: types.Limits{
						MaxOutputTokens: sm.MaxOutputTokens,
					},
				},
			})
		}
	}
	return dynamic
}

// NeedsPeriodicRefresh implements ModelProvider interface for Copilot model provider.
// Copilot provider needs periodic refresh since models can be added or updated on GitHub's side.
func (cmp *ModelProvider) NeedsPeriodicRefresh() bool {
	return true
}

// fetchStaticModels converts the static model list to []types.Model.
func (cmp *ModelProvider) fetchStaticModels() ([]types.Model, error) {
	modelsList := make([]types.Model, 0, len(cmp.staticModels))
	now := time.Now().Unix()
	for _, m := range cmp.staticModels {
		modelsList = append(modelsList, types.Model{
			ID:      m.ID,
			Object:  "model",
			Created: now,
			OwnedBy: "copilot",
			Capabilities: types.Capabilities{
				Type: "chat",
				Limits: types.Limits{
					MaxOutputTokens: m.MaxOutputTokens,
				},
			},
		})
	}
	return modelsList, nil
}
