// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package org

import (
	"net/http"
	"path"
	"strings"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
	repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup/markdown"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
	shared_user "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/web/shared/user"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

const tplOrgWiki templates.TplName = "org/wiki/view"

// OrgWikiPage represents a single page in the org wiki sidebar.
type OrgWikiPage struct {
	Name   string
	SubURL string
}

// Wiki renders the org wiki tab.
func Wiki(ctx *context.Context) {
	org := ctx.Org.Organization

	if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
		ctx.ServerError("RenderUserOrgHeader", err)
		return
	}

	ctx.Data["PageIsViewWiki"] = true
	ctx.Data["Title"] = org.DisplayName() + " - Wiki"

	// Determine which wiki repo to use (public vs member).
	viewAs := ctx.FormString("view_as", util.Iif(ctx.Org.IsMember, "member", "public"))
	viewAsMember := viewAs == "member"

	wikiRepo, commit := findOrgWikiCommit(ctx, org.ID, util.Iif(viewAsMember, shared_user.RepoNameWikiPrivate, shared_user.RepoNameWikiPublic))
	if wikiRepo == nil && viewAsMember {
		// Fall back to public wiki if member wiki doesn't exist.
		wikiRepo, commit = findOrgWikiCommit(ctx, org.ID, shared_user.RepoNameWikiPublic)
		viewAsMember = false
	}
	if wikiRepo == nil && !viewAsMember {
		// Fall back to member wiki if public wiki doesn't exist.
		wikiRepo, commit = findOrgWikiCommit(ctx, org.ID, shared_user.RepoNameWikiPrivate)
		viewAsMember = true
	}

	ctx.Data["IsViewingWikiAsMember"] = viewAsMember

	// Check whether both repos exist (for the dropdown toggle).
	publicExists := shared_user.OrgWikiRepoExists(ctx, org.ID, shared_user.RepoNameWikiPublic)
	privateExists := shared_user.OrgWikiRepoExists(ctx, org.ID, shared_user.RepoNameWikiPrivate)
	ctx.Data["ShowWikiViewSelector"] = publicExists && privateExists && ctx.Org.IsMember

	if wikiRepo == nil || commit == nil {
		ctx.Data["WikiEmpty"] = true
		ctx.HTML(http.StatusOK, tplOrgWiki)
		return
	}
	ctx.Data["WikiRepoLink"] = wikiRepo.Link()

	// Build page list from repo root.
	entries, err := commit.ListEntries()
	if err != nil {
		ctx.ServerError("ListEntries", err)
		return
	}
	pages := make([]OrgWikiPage, 0, len(entries))
	for _, entry := range entries {
		if !entry.IsRegular() {
			continue
		}
		name := entry.Name()
		if !isMarkdownFile(name) {
			continue
		}
		displayName := strings.TrimSuffix(name, path.Ext(name))
		if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
			continue
		}
		pages = append(pages, OrgWikiPage{
			Name:   displayName,
			SubURL: displayName,
		})
	}
	ctx.Data["Pages"] = pages

	// Determine which page to render.
	pageName := ctx.PathParamRaw("*")
	if pageName == "" {
		pageName = "Home"
	}
	ctx.Data["CurrentPage"] = pageName

	// Try to find the file: exact match, then with .md extension.
	blob := findWikiBlob(commit, pageName)
	if blob == nil {
		// Page not found — show empty state with page list.
		ctx.Data["WikiPageNotFound"] = true
		ctx.HTML(http.StatusOK, tplOrgWiki)
		return
	}

	content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
	if err != nil {
		ctx.ServerError("GetBlobContent", err)
		return
	}

	rctx := renderhelper.NewRenderContextRepoFile(ctx, wikiRepo, renderhelper.RepoFileOptions{
		CurrentRefPath: path.Join("branch", util.PathEscapeSegments(wikiRepo.DefaultBranch)),
	})
	renderedContent, err := markdown.RenderString(rctx, content)
	if err != nil {
		log.Error("Failed to render org wiki page %q: %v", pageName, err)
		ctx.ServerError("RenderString", err)
		return
	}
	ctx.Data["WikiContent"] = renderedContent

	// Render _Sidebar if it exists.
	sidebarBlob := findWikiBlob(commit, "_Sidebar")
	if sidebarBlob != nil {
		sidebarContent, err := sidebarBlob.GetBlobContent(setting.UI.MaxDisplayFileSize)
		if err == nil {
			rendered, err := markdown.RenderString(rctx, sidebarContent)
			if err == nil {
				ctx.Data["WikiSidebarHTML"] = rendered
			}
		}
	}

	// Render _Footer if it exists.
	footerBlob := findWikiBlob(commit, "_Footer")
	if footerBlob != nil {
		footerContent, err := footerBlob.GetBlobContent(setting.UI.MaxDisplayFileSize)
		if err == nil {
			rendered, err := markdown.RenderString(rctx, footerContent)
			if err == nil {
				ctx.Data["WikiFooterHTML"] = rendered
			}
		}
	}

	ctx.HTML(http.StatusOK, tplOrgWiki)
}

// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .profile.wiki.git).
func findOrgWikiCommit(ctx *context.Context, orgID int64, repoName string) (*repo_model.Repository, *git.Commit) {
	dbRepo, err := repo_model.GetRepositoryByName(ctx, orgID, repoName)
	if err != nil {
		if !repo_model.IsErrRepoNotExist(err) {
			log.Error("findOrgWikiCommit: GetRepositoryByName(%d, %s): %v", orgID, repoName, err)
		}
		return nil, nil
	}

	// Open the wiki git repo (.wiki.git sidecar), not the main repo.
	wikiGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, dbRepo.WikiStorageRepo())
	if err != nil {
		// Wiki repo doesn't exist yet — not an error, just no wiki.
		return nil, nil
	}

	branch := dbRepo.DefaultWikiBranch
	if branch == "" {
		branch = "main"
	}
	commit, err := wikiGitRepo.GetBranchCommit(branch)
	if err != nil {
		log.Error("findOrgWikiCommit: GetBranchCommit wiki(%s, %s): %v", dbRepo.FullName(), branch, err)
		return nil, nil
	}

	return dbRepo, commit
}

// findWikiBlob looks up a markdown file in the commit by name.
// Tries exact match first, then appends .md.
func findWikiBlob(commit *git.Commit, name string) *git.Blob {
	// Try exact match (e.g., "Home.md").
	if blob, _ := commit.GetBlobByPath(name); blob != nil {
		return blob
	}
	// Try with .md extension (e.g., "Home" → "Home.md").
	if blob, _ := commit.GetBlobByPath(name + ".md"); blob != nil {
		return blob
	}
	// Try with .markdown extension.
	if blob, _ := commit.GetBlobByPath(name + ".markdown"); blob != nil {
		return blob
	}
	return nil
}

// isMarkdownFile returns true if the filename looks like a markdown file.
func isMarkdownFile(name string) bool {
	ext := strings.ToLower(path.Ext(name))
	return ext == ".md" || ext == ".markdown"
}
