package repo

import (
	"net/http"
	"net/url"

	repo_model "code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/setting"
	api "code.gitea.io/gitea/modules/structs"
	util "code.gitea.io/gitea/modules/util"
	"code.gitea.io/gitea/services/convert"
	wiki_service "code.gitea.io/gitea/services/wiki"
)

func ListWikiPageNames(ctx *context.APIContext) {

	wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
	if err != nil {
		if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
			ctx.NotFound(err)
		} else {
			ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
		}
		return
	}

	commit, err := wikiRepo.GetBranchCommit("master")
	if err != nil {
		if git.IsErrNotExist(err) {
			ctx.NotFound(err)
		} else {
			ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
		}
		return
	}

	if wikiRepo != nil {

		defer wikiRepo.Close()
	}

	if ctx.Written() {
		return
	}

	entries, err := commit.ListEntries()
	if err != nil {
		ctx.ServerError("ListEntries", err)
		return
	}

	pages := make([]*api.WikiPageMetaData, 0, len(entries))
	for _, entry := range entries {
		if !entry.IsRegular() {
			continue
		}
		wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
		if err != nil {
			if repo_model.IsErrWikiInvalidFileName(err) {
				continue
			}
			ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
			return
		}
		subURL := string(wikiName)
		_, title := wiki_service.WebPathToUserTitle(wikiName)
		pages = append(pages, &api.WikiPageMetaData{
			Title:   title,
			HTMLURL: util.URLJoin(ctx.Repo.Repository.HTMLURL(), "wiki", subURL),
			SubURL:  subURL,
		})
	}

	ctx.SetTotalCountHeader(int64(len(entries)))
	ctx.JSON(http.StatusOK, pages)
}

func ShowRevision(ctx *context.APIContext) {
	wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
	if err != nil {
		if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
			ctx.NotFound(err)
		} else {
			ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
		}
		return
	}

	if wikiRepo != nil {

		defer wikiRepo.Close()
	}

	// get requested pagename
	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
	if len(pageName) == 0 {
		pageName = "Home"
	}

	revision := ctx.PathParamRaw(":revision")

	commit, err := wikiRepo.GetCommit(revision)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetCommit", err)
		return
	}

	content, pageFilename := wikiContentsByName(ctx, commit, pageName, false)

	sidebarContent, _ := wikiContentsByName(ctx, commit, "_Sidebar", true)

	footerContent, _ := wikiContentsByName(ctx, commit, "_Footer", true)

	// get commit count - wiki revisions
	commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)

	// Get last change information.
	lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err)
		return
	}

	ctx.JSON(http.StatusOK, &api.WikiPage{
		WikiPageMetaData: convert.ToWikiPageMetaData(pageName, lastCommit, ctx.Repo.Repository),
		ContentBase64:    content,
		CommitCount:      commitsCount,
		Sidebar:          sidebarContent,
		Footer:           footerContent,
	})

}

// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
	entry, err := commit.GetTreeEntryByPath(target)
	if err != nil {
		return nil, err
	}
	if entry != nil {
		return entry, nil
	}

	// Then the unescaped, shortest alternative
	var unescapedTarget string
	if unescapedTarget, err = url.QueryUnescape(target); err != nil {
		return nil, err
	}
	return commit.GetTreeEntryByPath(unescapedTarget)
}

// wikiContentsByEntry returns the contents of the wiki page referenced by the
// given tree entry, encoded with base64. Writes to ctx if an error occurs.
func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
	blob := entry.Blob()
	if blob.Size() > setting.API.DefaultMaxBlobSize {
		return ""
	}
	content, err := blob.GetBlobContentBase64()
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err)
		return ""
	}
	return content
}

// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wiki_service.WebPath, isSidebarOrFooter bool) (string, string) {
	gitFilename := wiki_service.WebPathToGitPath(wikiName)
	entry, err := findEntryForFile(commit, gitFilename)
	if err != nil {
		if git.IsErrNotExist(err) {
			if !isSidebarOrFooter {
				ctx.NotFound()
			}
		} else {
			ctx.ServerError("findEntryForFile", err)
		}
		return "", ""
	}
	return wikiContentsByEntry(ctx, entry), gitFilename
}
