package gitea

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"net/http"
	"sync"

	mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
	"gitea.com/gitea/gitea-mcp/pkg/flag"

	"gitea.dev/sdk"
)

var (
	clientCache     sync.Map // token -> *gitea.Client
	sharedTransOnce sync.Once
	sharedTrans     *http.Transport
)

func sharedTransport() *http.Transport {
	sharedTransOnce.Do(func() {
		sharedTrans = http.DefaultTransport.(*http.Transport).Clone()
		if flag.Insecure {
			sharedTrans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // user-requested insecure mode
		}
	})
	return sharedTrans
}

// NewClient returns a cached *gitea.Client keyed by host+token. The SDK's per-client
// version cache and the shared transport let us reuse keep-alive connections
// and avoid the SDK's /api/v1/version preflight on every tool call.
func NewClient(token string) (*gitea.Client, error) {
	key := flag.Host + "\x00" + token
	if v, ok := clientCache.Load(key); ok {
		return v.(*gitea.Client), nil
	}

	httpClient := &http.Client{
		Transport:     sharedTransport(),
		CheckRedirect: checkRedirect,
	}
	opts := []gitea.ClientOption{
		gitea.SetToken(token),
		gitea.SetHTTPClient(httpClient),
	}
	if flag.Debug {
		opts = append(opts, gitea.SetDebugMode())
	}
	client, err := gitea.NewClient(flag.Host, opts...)
	if err != nil {
		return nil, fmt.Errorf("create gitea client err: %w", err)
	}
	client.SetUserAgent("gitea-mcp-server/" + flag.Version)

	actual, _ := clientCache.LoadOrStore(key, client)
	return actual.(*gitea.Client), nil
}

// checkRedirect prevents Go from silently changing mutating requests (POST, PATCH, etc.)
// to GET when following 301/302/303 redirects, which would drop the request body and
// make writes appear to succeed when they didn't.
func checkRedirect(_ *http.Request, via []*http.Request) error {
	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	if via[0].Method != http.MethodGet && via[0].Method != http.MethodHead {
		return http.ErrUseLastResponse
	}
	return nil
}

func ClientFromContext(ctx context.Context) (*gitea.Client, error) {
	token, ok := ctx.Value(mcpContext.TokenContextKey).(string)
	if !ok {
		token = flag.Token
	}
	return NewClient(token)
}
