package test

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"path/filepath"
	"sync/atomic"
	"testing"

	"github.com/armon/go-socks5"
	"github.com/gliderlabs/ssh"
	"github.com/go-git/go-git/v5/plumbing/transport"
	ggssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"

	fixtures "github.com/go-git/go-git-fixtures/v4"
	stdssh "golang.org/x/crypto/ssh"
	. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type ProxyEnvSuite struct {
	fixtures.Suite
	port int
	base string
}

var _ = Suite(&ProxyEnvSuite{})

var socksProxiedRequests int32

// This test tests proxy support via an env var, i.e. `ALL_PROXY`.
// Its located in a separate package because golang caches the value
// of proxy env vars leading to misleading/unexpected test results.
func (s *ProxyEnvSuite) TestCommand(c *C) {
	socksListener, err := net.Listen("tcp", "localhost:0")
	c.Assert(err, IsNil)

	socksServer, err := socks5.New(&socks5.Config{
		Rules: TestProxyRule{},
	})
	c.Assert(err, IsNil)
	go func() {
		socksServer.Serve(socksListener)
	}()
	socksProxyAddr := fmt.Sprintf("socks5://localhost:%d", socksListener.Addr().(*net.TCPAddr).Port)
	os.Setenv("ALL_PROXY", socksProxyAddr)
	defer os.Unsetenv("ALL_PROXY")

	sshListener, err := net.Listen("tcp", "localhost:0")
	c.Assert(err, IsNil)
	sshServer := &ssh.Server{Handler: HandlerSSH}
	go func() {
		log.Fatal(sshServer.Serve(sshListener))
	}()

	s.port = sshListener.Addr().(*net.TCPAddr).Port
	s.base, err = os.MkdirTemp(c.MkDir(), fmt.Sprintf("go-git-ssh-%d", s.port))
	c.Assert(err, IsNil)

	ggssh.DefaultAuthBuilder = func(user string) (ggssh.AuthMethod, error) {
		return &ggssh.Password{User: user}, nil
	}

	ep := s.prepareRepository(c, fixtures.Basic().One(), "basic.git")
	c.Assert(err, IsNil)

	client := ggssh.NewClient(&stdssh.ClientConfig{
		HostKeyCallback: stdssh.InsecureIgnoreHostKey(),
	})
	r, err := client.NewUploadPackSession(ep, nil)
	c.Assert(err, IsNil)
	defer func() { c.Assert(r.Close(), IsNil) }()

	info, err := r.AdvertisedReferences()
	c.Assert(err, IsNil)
	c.Assert(info, NotNil)
	proxyUsed := atomic.LoadInt32(&socksProxiedRequests) > 0
	c.Assert(proxyUsed, Equals, true)
}

func (s *ProxyEnvSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *transport.Endpoint {
	fs := f.DotGit()

	err := fixtures.EnsureIsBare(fs)
	c.Assert(err, IsNil)

	path := filepath.Join(s.base, name)
	err = os.Rename(fs.Root(), path)
	c.Assert(err, IsNil)

	return s.newEndpoint(c, name)
}

func (s *ProxyEnvSuite) newEndpoint(c *C, name string) *transport.Endpoint {
	ep, err := transport.NewEndpoint(fmt.Sprintf(
		"ssh://git@localhost:%d/%s/%s", s.port, filepath.ToSlash(s.base), name,
	))

	c.Assert(err, IsNil)
	return ep
}

type TestProxyRule struct{}

func (dr TestProxyRule) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) {
	atomic.AddInt32(&socksProxiedRequests, 1)
	return ctx, true
}
