package agentteambridge

import (
	"context"
	"log/slog"
	"net/http"
	"sync"

	"github.com/tidbcloud/AgentTeam/internal/apr/provider/codex"
	"github.com/tidbcloud/AgentTeam/internal/apr/runtime"
	"github.com/tidbcloud/AgentTeam/internal/apr/types"
)

type Refresher struct {
	dispatcher  *runtime.Dispatcher
	loader      OrganizationProviderLoader
	clients     ProviderHTTPClients
	transporter types.HTTPProxy
	logger      *slog.Logger
	lifecycle   context.Context
	cancel      context.CancelFunc
	cleanupFns  []func()
	closed      bool
	mu          sync.RWMutex

	codexProviders map[string]*codex.Codex
}

type RefresherOptions struct {
	HTTPClients      ProviderHTTPClients
	Transporter      types.HTTPProxy
	Logger           *slog.Logger
	LifecycleContext context.Context
}

func NewRefresher(loader OrganizationProviderLoader, httpClient *http.Client, transporter types.HTTPProxy, logger *slog.Logger) *Refresher {
	return NewRefresherWithOptions(loader, RefresherOptions{
		HTTPClients: ProviderHTTPClients{Upstream: httpClient, Setup: httpClient},
		Transporter: transporter,
		Logger:      logger,
	})
}

func NewRefresherWithOptions(loader OrganizationProviderLoader, options RefresherOptions) *Refresher {
	lifecycle := options.LifecycleContext
	if lifecycle == nil {
		lifecycle = context.Background()
	}
	lifecycle, cancel := context.WithCancel(lifecycle)
	return &Refresher{
		dispatcher:     runtime.NewDispatcher(),
		loader:         loader,
		clients:        options.HTTPClients.normalized(),
		transporter:    options.Transporter,
		logger:         options.Logger,
		lifecycle:      lifecycle,
		cancel:         cancel,
		codexProviders: make(map[string]*codex.Codex),
	}
}

func (refresher *Refresher) Reload(ctx context.Context) (LoadResult, error) {
	refresher.mu.Lock()
	defer refresher.mu.Unlock()

	if refresher.closed {
		return LoadResult{}, context.Canceled
	}
	lifecycle := refresher.lifecycle
	if lifecycle == nil {
		lifecycle = context.Background()
	}
	providerLifecycle, cancelProviderLifecycle := context.WithCancel(lifecycle)
	result, cleanupFns, codexProviders, err := applyOrganizationProvidersWithHTTPClients(ctx, providerLifecycle, refresher.dispatcher, refresher.loader, refresher.clients, refresher.transporter)
	if err != nil {
		cancelProviderLifecycle()
		return LoadResult{}, err
	}
	cleanupFns = append([]func(){cancelProviderLifecycle}, cleanupFns...)
	oldCleanupFns := refresher.cleanupFns
	refresher.cleanupFns = cleanupFns
	refresher.codexProviders = codexProviders
	cleanupFunctions(oldCleanupFns)
	if refresher.logger != nil {
		refresher.logger.Info("reloaded apr organization provider registry",
			slog.Int("providers", result.ProviderCount),
			slog.Int("routes", result.RouteCount),
			slog.Int("tokens", result.TokenCount),
			slog.Int("skipped", result.SkippedCount),
		)
	}
	return result, nil
}

func (refresher *Refresher) Refresh(ctx context.Context) error {
	_, err := refresher.Reload(ctx)
	return err
}

func (refresher *Refresher) Close() {
	if refresher == nil {
		return
	}

	refresher.mu.Lock()
	if refresher.closed {
		refresher.mu.Unlock()
		return
	}
	refresher.closed = true
	cleanupFns := refresher.cleanupFns
	refresher.cleanupFns = nil
	refresher.codexProviders = make(map[string]*codex.Codex)
	cancel := refresher.cancel
	refresher.cancel = nil
	refresher.mu.Unlock()

	cleanupFunctions(cleanupFns)
	if cancel != nil {
		cancel()
	}
}

func (refresher *Refresher) APRDispatcher() *runtime.Dispatcher {
	if refresher == nil {
		return nil
	}
	return refresher.dispatcher
}
