// Package gittransport owns the dedicated go-git transport for embedded AGS.
package gittransport

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/url"
	"sync"

	"github.com/go-git/go-git/v5/plumbing/transport"
	gitclient "github.com/go-git/go-git/v5/plumbing/transport/client"
	githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
	"github.com/tidbcloud/AgentTeam/internal/inprochttp"
)

const protocol = "meshxags"

var handlers = struct {
	sync.RWMutex
	byHost map[string]http.Handler
}{byHost: make(map[string]http.Handler)}

// Install the private protocol before any runtime can serve concurrent Git
// work. The go-git protocol map is not concurrency-safe, so it must never be
// mutated again after package initialization.
func init() {
	if _, exists := gitclient.Protocols[protocol]; exists {
		panic("go-git protocol meshxags is already installed")
	}
	gitclient.InstallProtocol(protocol, registryTransport{})
}

// Register adds one embedded AGS handler to the transport's private registry.
// Unregister only changes this package's protected registry; it never mutates
// go-git's process-global protocol map.
func Register(handler http.Handler) (string, func(), error) {
	if handler == nil {
		return "", nil, fmt.Errorf("embedded Git handler is required")
	}
	host, err := newHost()
	if err != nil {
		return "", nil, err
	}
	handlers.Lock()
	handlers.byHost[host] = handler
	handlers.Unlock()

	var once sync.Once
	return protocol + "://" + host, func() {
		once.Do(func() {
			handlers.Lock()
			delete(handlers.byHost, host)
			handlers.Unlock()
		})
	}, nil
}

// ValidateRegisteredBaseURL accepts only a process-local endpoint returned by
// Register. Callers append repository paths after this check; the registered
// base itself therefore cannot carry URL authority or path modifiers.
func ValidateRegisteredBaseURL(raw string) error {
	parsed, err := url.Parse(raw)
	if err != nil || parsed.Scheme != protocol || parsed.Opaque != "" || parsed.User != nil ||
		parsed.Host == "" || parsed.Host != parsed.Hostname() || parsed.Path != "" ||
		parsed.RawPath != "" || parsed.RawQuery != "" || parsed.ForceQuery || parsed.Fragment != "" || parsed.RawFragment != "" {
		return fmt.Errorf("embedded Git base URL is invalid")
	}
	handlers.RLock()
	_, registered := handlers.byHost[parsed.Host]
	handlers.RUnlock()
	if !registered {
		return fmt.Errorf("embedded Git endpoint %q is not registered", parsed.Host)
	}
	return nil
}

func newHost() (string, error) {
	var id [16]byte
	if _, err := rand.Read(id[:]); err != nil {
		return "", fmt.Errorf("generate embedded Git endpoint: %w", err)
	}
	return hex.EncodeToString(id[:]) + ".embedded.invalid", nil
}

type registryTransport struct{}

func (registryTransport) NewUploadPackSession(endpoint *transport.Endpoint, auth transport.AuthMethod) (transport.UploadPackSession, error) {
	next, clone, err := transportFor(endpoint)
	if err != nil {
		return nil, err
	}
	return next.NewUploadPackSession(clone, auth)
}

func (registryTransport) NewReceivePackSession(endpoint *transport.Endpoint, auth transport.AuthMethod) (transport.ReceivePackSession, error) {
	next, clone, err := transportFor(endpoint)
	if err != nil {
		return nil, err
	}
	return next.NewReceivePackSession(clone, auth)
}

func transportFor(endpoint *transport.Endpoint) (transport.Transport, *transport.Endpoint, error) {
	if endpoint == nil {
		return nil, nil, fmt.Errorf("embedded Git endpoint is required")
	}
	handlers.RLock()
	handler := handlers.byHost[endpoint.Host]
	handlers.RUnlock()
	if handler == nil {
		return nil, nil, fmt.Errorf("embedded Git endpoint %q is not registered", endpoint.Host)
	}

	client := &http.Client{Transport: inprochttp.RoundTripper{Handler: handler, Scheme: "http"}}
	client.CheckRedirect = func(request *http.Request, via []*http.Request) error {
		if len(via) > 0 {
			request.URL.Scheme = "http"
			request.URL.Host = endpoint.Host
			request.Host = endpoint.Host
		}
		return nil
	}
	clone := *endpoint
	clone.Protocol = "http"
	return githttp.NewClient(client), &clone, nil
}
