// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
	"encoding/base64"
	"fmt"
	"path"

	"github.com/gogs/git-module"
	"github.com/pkg/errors"

	"gogs.io/gogs/internal/context"
	"gogs.io/gogs/internal/gitutil"
)

func GetContents(c *context.APIContext) {
	gitRepo, err := git.Open(c.Repo.Repository.RepoPath())
	if err != nil {
		c.Error(err, "open repository")
		return
	}

	ref := c.Query("ref")
	if ref == "" {
		ref = c.Repo.Repository.DefaultBranch
	}

	commit, err := gitRepo.CatFileCommit(ref)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get commit")
		return
	}

	treePath := c.Params("*")
	entry, err := commit.TreeEntry(treePath)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree entry")
		return
	}

	type links struct {
		Git  string `json:"git"`
		Self string `json:"self"`
		HTML string `json:"html"`
	}
	type repoContent struct {
		Type            string `json:"type"`
		Target          string `json:"target,omitempty"`
		SubmoduleGitURL string `json:"submodule_git_url,omitempty"`
		Encoding        string `json:"encoding,omitempty"`
		Size            int64  `json:"size"`
		Name            string `json:"name"`
		Path            string `json:"path"`
		Content         string `json:"content,omitempty"`
		Sha             string `json:"sha"`
		URL             string `json:"url"`
		GitURL          string `json:"git_url"`
		HTMLURL         string `json:"html_url"`
		DownloadURL     string `json:"download_url"`
		Links           links  `json:"_links"`
	}

	toRepoContent := func(subpath string, entry *git.TreeEntry) (*repoContent, error) {
		repoURL := fmt.Sprintf("%s/repos/%s/%s", c.BaseURL, c.Params(":username"), c.Params(":reponame"))
		selfURL := fmt.Sprintf("%s/contents/%s", repoURL, subpath)
		htmlURL := fmt.Sprintf("%s/src/%s/%s", c.Repo.Repository.HTMLURL(), ref, entry.Name())
		downloadURL := fmt.Sprintf("%s/raw/%s/%s", c.Repo.Repository.HTMLURL(), ref, entry.Name())

		content := &repoContent{
			Size:        entry.Size(),
			Name:        entry.Name(),
			Path:        subpath,
			Sha:         entry.ID().String(),
			URL:         selfURL,
			HTMLURL:     htmlURL,
			DownloadURL: downloadURL,
			Links: links{
				Self: selfURL,
				HTML: htmlURL,
			},
		}

		switch {
		case entry.IsBlob(), entry.IsExec():
			content.Type = "file"
			p, err := entry.Blob().Bytes()
			if err != nil {
				return nil, errors.Wrap(err, "get blob content")
			}
			content.Encoding = "base64"
			content.Content = base64.StdEncoding.EncodeToString(p)
			content.GitURL = fmt.Sprintf("%s/git/blobs/%s", repoURL, entry.ID().String())

		case entry.IsTree():
			content.Type = "dir"
			content.GitURL = fmt.Sprintf("%s/git/trees/%s", repoURL, entry.ID().String())

		case entry.IsSymlink():
			content.Type = "symlink"
			p, err := entry.Blob().Bytes()
			if err != nil {
				return nil, errors.Wrap(err, "get blob content")
			}
			content.Target = string(p)

		case entry.IsCommit():
			content.Type = "submodule"
			mod, err := commit.Submodule(subpath)
			if err != nil {
				return nil, errors.Wrap(err, "get submodule")
			}
			content.SubmoduleGitURL = mod.URL

		default:
			panic("unreachable")
		}

		content.Links.Git = content.GitURL

		return content, nil
	}

	if !entry.IsTree() {
		content, err := toRepoContent(treePath, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", treePath)
			return
		}

		c.JSONSuccess(content)
		return
	}

	// The entry is a directory
	dir, err := gitRepo.LsTree(entry.ID().String())
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree")
		return
	}

	entries, err := dir.Entries()
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "list entries")
		return
	}

	if len(entries) == 0 {
		c.JSONSuccess([]string{})
		return
	}

	contents := make([]*repoContent, 0, len(entries))
	for _, entry := range entries {
		subpath := path.Join(treePath, entry.Name())
		content, err := toRepoContent(subpath, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", subpath)
			return
		}

		contents = append(contents, content)
	}
	c.JSONSuccess(contents)
}