package serverinfo

import (
	"io"
	"strings"
	"testing"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-billy/v5/memfs"
	fixtures "github.com/go-git/go-git-fixtures/v4"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/go-git/go-git/v5/plumbing/storer"
	"github.com/go-git/go-git/v5/storage"
	"github.com/go-git/go-git/v5/storage/memory"
	. "gopkg.in/check.v1"
)

type ServerInfoSuite struct{}

var _ = Suite(&ServerInfoSuite{})

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

func (s *ServerInfoSuite) TestUpdateServerInfoInit(c *C) {
	fs := memfs.New()
	st := memory.NewStorage()
	r, err := git.Init(st, fs)
	c.Assert(err, IsNil)
	c.Assert(r, NotNil)

	err = UpdateServerInfo(st, fs)
	c.Assert(err, IsNil)
}

func assertInfoRefs(c *C, st storage.Storer, fs billy.Filesystem) {
	refsFile, err := fs.Open("info/refs")
	c.Assert(err, IsNil)

	defer refsFile.Close()
	bts, err := io.ReadAll(refsFile)
	c.Assert(err, IsNil)

	localRefs := make(map[plumbing.ReferenceName]plumbing.Hash)
	for _, line := range strings.Split(string(bts), "\n") {
		if line == "" {
			continue
		}
		parts := strings.Split(line, "\t")
		c.Assert(parts, HasLen, 2)
		hash := plumbing.NewHash(parts[0])
		name := plumbing.ReferenceName(parts[1])
		localRefs[name] = hash
	}

	refs, err := st.IterReferences()
	c.Assert(err, IsNil)

	err = refs.ForEach(func(ref *plumbing.Reference) error {
		name := ref.Name()
		hash := ref.Hash()
		switch ref.Type() {
		case plumbing.SymbolicReference:
			if name == plumbing.HEAD {
				return nil
			}
			ref, err := st.Reference(ref.Target())
			c.Assert(err, IsNil)
			hash = ref.Hash()
			fallthrough
		case plumbing.HashReference:
			h, ok := localRefs[name]
			c.Assert(ok, Equals, true)
			c.Assert(h, Equals, hash)
			if name.IsTag() {
				tag, err := object.GetTag(st, hash)
				if err == nil {
					t, ok := localRefs[name+"^{}"]
					c.Assert(ok, Equals, true)
					c.Assert(t, Equals, tag.Target)
				}
			}
		}
		return nil
	})

	c.Assert(err, IsNil)
}

func assertObjectPacks(c *C, st storage.Storer, fs billy.Filesystem) {
	infoPacks, err := fs.Open("objects/info/packs")
	c.Assert(err, IsNil)

	defer infoPacks.Close()
	bts, err := io.ReadAll(infoPacks)
	c.Assert(err, IsNil)

	pos, ok := st.(storer.PackedObjectStorer)
	c.Assert(ok, Equals, true)
	localPacks := make(map[string]struct{})
	packs, err := pos.ObjectPacks()
	c.Assert(err, IsNil)

	for _, line := range strings.Split(string(bts), "\n") {
		if line == "" {
			continue
		}
		parts := strings.Split(line, " ")
		c.Assert(parts, HasLen, 2)
		pack := strings.TrimPrefix(parts[1], "pack-")
		pack = strings.TrimSuffix(pack, ".pack")
		localPacks[pack] = struct{}{}
	}

	for _, p := range packs {
		_, ok := localPacks[p.String()]
		c.Assert(ok, Equals, true)
	}
}

func (s *ServerInfoSuite) TestUpdateServerInfoTags(c *C) {
	fs := memfs.New()
	st := memory.NewStorage()
	r, err := git.Clone(st, fs, &git.CloneOptions{
		URL: fixtures.ByURL("https://github.com/git-fixtures/tags.git").One().URL,
	})
	c.Assert(err, IsNil)
	c.Assert(r, NotNil)

	err = UpdateServerInfo(st, fs)
	c.Assert(err, IsNil)

	assertInfoRefs(c, st, fs)
	assertObjectPacks(c, st, fs)
}

func (s *ServerInfoSuite) TestUpdateServerInfoBasic(c *C) {
	fs := memfs.New()
	st := memory.NewStorage()
	r, err := git.Clone(st, fs, &git.CloneOptions{
		URL: fixtures.Basic().One().URL,
	})
	c.Assert(err, IsNil)
	c.Assert(r, NotNil)

	err = UpdateServerInfo(st, fs)
	c.Assert(err, IsNil)

	assertInfoRefs(c, st, fs)
	assertObjectPacks(c, st, fs)
}

func (s *ServerInfoSuite) TestUpdateServerInfoBasicChange(c *C) {
	fs := memfs.New()
	st := memory.NewStorage()
	r, err := git.Clone(st, fs, &git.CloneOptions{
		URL: fixtures.Basic().One().URL,
	})
	c.Assert(err, IsNil)
	c.Assert(r, NotNil)

	err = UpdateServerInfo(st, fs)
	c.Assert(err, IsNil)

	assertInfoRefs(c, st, fs)
	assertObjectPacks(c, st, fs)

	head, err := r.Head()
	c.Assert(err, IsNil)

	ref := plumbing.NewHashReference("refs/heads/my-branch", head.Hash())
	err = r.Storer.SetReference(ref)
	c.Assert(err, IsNil)

	_, err = r.CreateTag("test-tag", head.Hash(), &git.CreateTagOptions{
		Message: "test-tag",
	})
	c.Assert(err, IsNil)

	err = UpdateServerInfo(st, fs)
	c.Assert(err, IsNil)

	assertInfoRefs(c, st, fs)
	assertObjectPacks(c, st, fs)
}
