// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT

package pages

import (
	"errors"
	"html/template"
	"net/http"
	"path"
	"strings"

	"code.gitea.io/gitea/models/renderhelper"
	repo_model "code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/markup/markdown"
	pages_module "code.gitea.io/gitea/modules/pages"
	"code.gitea.io/gitea/modules/templates"
	"code.gitea.io/gitea/services/context"
	pages_service "code.gitea.io/gitea/services/pages"
)

const (
	tplPagesSimple        templates.TplName = "pages/simple"
	tplPagesDocumentation templates.TplName = "pages/documentation"
	tplPagesProduct       templates.TplName = "pages/product"
	tplPagesPortfolio     templates.TplName = "pages/portfolio"
)

// ServeLandingPage serves the landing page for a repository
func ServeLandingPage(ctx *context.Context) {
	// Get the repository from subdomain or custom domain
	repo, config, err := getRepoFromRequest(ctx)
	if err != nil {
		log.Error("Failed to get repo from pages request: %v", err)
		ctx.NotFound(err)
		return
	}

	if repo == nil || config == nil || !config.Enabled {
		ctx.NotFound(errors.New("pages not configured"))
		return
	}

	// Check for redirect
	requestPath := ctx.Req.URL.Path
	if config.Advanced.Redirects != nil {
		if redirect, ok := config.Advanced.Redirects[requestPath]; ok {
			ctx.Redirect(redirect)
			return
		}
	}

	// Render the landing page
	renderLandingPage(ctx, repo, config)
}

// getRepoFromRequest extracts the repository from the pages request
func getRepoFromRequest(ctx *context.Context) (*repo_model.Repository, *pages_module.LandingConfig, error) {
	host := ctx.Req.Host

	// Check for custom domain first
	repo, err := pages_service.GetRepoByPagesDomain(ctx, host)
	if err == nil && repo != nil {
		config, err := pages_service.GetPagesConfig(ctx, repo)
		if err != nil {
			return nil, nil, err
		}
		return repo, config, nil
	}

	// Parse subdomain: {repo}-{owner}.{domain}
	parts := strings.Split(host, ".")
	if len(parts) < 2 {
		return nil, nil, errors.New("invalid pages subdomain")
	}

	// First part is {repo}-{owner}
	repoOwner := strings.SplitN(parts[0], "-", 2)
	if len(repoOwner) != 2 {
		return nil, nil, errors.New("invalid pages subdomain format")
	}
	repoName := repoOwner[0]
	ownerName := repoOwner[1]

	repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
	if err != nil {
		return nil, nil, err
	}

	config, err := pages_service.GetPagesConfig(ctx, repo)
	if err != nil {
		return nil, nil, err
	}

	return repo, config, nil
}

// renderLandingPage renders the landing page based on the template
func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) {
	// Set up context data
	ctx.Data["Repository"] = repo
	ctx.Data["Config"] = config
	ctx.Data["Title"] = getPageTitle(repo, config)
	ctx.Data["PageIsPagesLanding"] = true

	// Load README content
	readme, err := loadReadmeContent(ctx, repo, config)
	if err != nil {
		log.Warn("Failed to load README: %v", err)
	}
	ctx.Data["ReadmeContent"] = readme

	// Load repo stats
	ctx.Data["NumStars"] = repo.NumStars
	ctx.Data["NumForks"] = repo.NumForks

	// Select template based on config
	tpl := selectTemplate(config.Template)

	ctx.HTML(http.StatusOK, tpl)
}

// getPageTitle returns the page title
func getPageTitle(repo *repo_model.Repository, config *pages_module.LandingConfig) string {
	if config.SEO.Title != "" {
		return config.SEO.Title
	}
	if config.Hero.Title != "" {
		return config.Hero.Title
	}
	return repo.Name
}

// loadReadmeContent loads and renders the README content
func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) (template.HTML, error) {
	gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
	if err != nil {
		return "", err
	}
	defer gitRepo.Close()

	branch := repo.DefaultBranch
	if branch == "" {
		branch = "main"
	}

	commit, err := gitRepo.GetBranchCommit(branch)
	if err != nil {
		return "", err
	}

	// Find README file
	readmePath := findReadmePath(commit, config)
	if readmePath == "" {
		return "", errors.New("README not found")
	}

	entry, err := commit.GetTreeEntryByPath(readmePath)
	if err != nil {
		return "", err
	}

	reader, err := entry.Blob().DataAsync()
	if err != nil {
		return "", err
	}
	defer reader.Close()

	content := make([]byte, entry.Blob().Size())
	_, err = reader.Read(content)
	if err != nil && err.Error() != "EOF" {
		return "", err
	}

	// Render markdown using renderhelper
	rctx := renderhelper.NewRenderContextRepoFile(ctx, repo)
	rendered, err := markdown.RenderString(rctx, string(content))
	if err != nil {
		return "", err
	}

	return rendered, nil
}

// findReadmePath finds the README file path
func findReadmePath(commit *git.Commit, config *pages_module.LandingConfig) string {
	// Check config for custom readme location
	for _, section := range config.Sections {
		if section.Type == "readme" && section.File != "" {
			return section.File
		}
	}

	// Default README locations
	readmePaths := []string{
		"README.md",
		"readme.md",
		"Readme.md",
		"README.markdown",
		"README.txt",
		"README",
	}

	for _, p := range readmePaths {
		if _, err := commit.GetTreeEntryByPath(p); err == nil {
			return p
		}
	}

	return ""
}

// selectTemplate selects the template based on configuration
func selectTemplate(templateName string) templates.TplName {
	switch templateName {
	case "documentation":
		return tplPagesDocumentation
	case "product":
		return tplPagesProduct
	case "portfolio":
		return tplPagesPortfolio
	default:
		return tplPagesSimple
	}
}

// ServePageAsset serves static assets for the landing page
func ServePageAsset(ctx *context.Context) {
	repo, _, err := getRepoFromRequest(ctx)
	if err != nil {
		ctx.NotFound(err)
		return
	}

	// Get the asset path from URL
	assetPath := strings.TrimPrefix(ctx.Req.URL.Path, "/assets/")
	if assetPath == "" {
		ctx.NotFound(errors.New("asset not found"))
		return
	}

	// Load asset from repository
	gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
	if err != nil {
		ctx.NotFound(err)
		return
	}
	defer gitRepo.Close()

	branch := repo.DefaultBranch
	if branch == "" {
		branch = "main"
	}

	commit, err := gitRepo.GetBranchCommit(branch)
	if err != nil {
		ctx.NotFound(err)
		return
	}

	// Try assets folder first
	fullPath := path.Join("assets", assetPath)
	entry, err := commit.GetTreeEntryByPath(fullPath)
	if err != nil {
		// Try .gitea/assets
		fullPath = path.Join(".gitea", "assets", assetPath)
		entry, err = commit.GetTreeEntryByPath(fullPath)
		if err != nil {
			ctx.NotFound(err)
			return
		}
	}

	reader, err := entry.Blob().DataAsync()
	if err != nil {
		ctx.ServerError("Failed to read asset", err)
		return
	}
	defer reader.Close()

	// Set content type based on extension
	ext := path.Ext(assetPath)
	contentType := getContentType(ext)
	ctx.Resp.Header().Set("Content-Type", contentType)
	ctx.Resp.Header().Set("Cache-Control", "public, max-age=3600")

	// Stream content
	content := make([]byte, entry.Blob().Size())
	_, err = reader.Read(content)
	if err != nil && err.Error() != "EOF" {
		ctx.ServerError("Failed to read asset", err)
		return
	}

	_, _ = ctx.Resp.Write(content)
}

// ServeRepoLandingPage serves the landing page for a repository via URL path
func ServeRepoLandingPage(ctx *context.Context) {
	repo := ctx.Repo.Repository
	if repo == nil {
		ctx.NotFound(errors.New("repository not found"))
		return
	}

	config, err := pages_service.GetPagesConfig(ctx, repo)
	if err != nil {
		log.Error("Failed to get pages config: %v", err)
		ctx.NotFound(err)
		return
	}

	if config == nil || !config.Enabled {
		ctx.NotFound(errors.New("pages not enabled for this repository"))
		return
	}

	// Render the landing page
	renderLandingPage(ctx, repo, config)
}

// ServeRepoPageAsset serves static assets for the landing page via URL path
func ServeRepoPageAsset(ctx *context.Context) {
	repo := ctx.Repo.Repository
	if repo == nil {
		ctx.NotFound(errors.New("repository not found"))
		return
	}

	// Get the asset path from URL
	assetPath := ctx.PathParam("*")
	if assetPath == "" {
		ctx.NotFound(errors.New("asset not found"))
		return
	}

	// Load asset from repository
	gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
	if err != nil {
		ctx.NotFound(err)
		return
	}
	defer gitRepo.Close()

	branch := repo.DefaultBranch
	if branch == "" {
		branch = "main"
	}

	commit, err := gitRepo.GetBranchCommit(branch)
	if err != nil {
		ctx.NotFound(err)
		return
	}

	// Try assets folder first
	fullPath := path.Join("assets", assetPath)
	entry, err := commit.GetTreeEntryByPath(fullPath)
	if err != nil {
		// Try .gitea/assets
		fullPath = path.Join(".gitea", "assets", assetPath)
		entry, err = commit.GetTreeEntryByPath(fullPath)
		if err != nil {
			ctx.NotFound(err)
			return
		}
	}

	reader, err := entry.Blob().DataAsync()
	if err != nil {
		ctx.ServerError("Failed to read asset", err)
		return
	}
	defer reader.Close()

	// Set content type based on extension
	ext := path.Ext(assetPath)
	contentType := getContentType(ext)
	ctx.Resp.Header().Set("Content-Type", contentType)
	ctx.Resp.Header().Set("Cache-Control", "public, max-age=3600")

	// Stream content
	content := make([]byte, entry.Blob().Size())
	_, err = reader.Read(content)
	if err != nil && err.Error() != "EOF" {
		ctx.ServerError("Failed to read asset", err)
		return
	}

	_, _ = ctx.Resp.Write(content)
}

// getContentType returns the content type for a file extension
func getContentType(ext string) string {
	types := map[string]string{
		".html":  "text/html",
		".css":   "text/css",
		".js":    "application/javascript",
		".json":  "application/json",
		".png":   "image/png",
		".jpg":   "image/jpeg",
		".jpeg":  "image/jpeg",
		".gif":   "image/gif",
		".svg":   "image/svg+xml",
		".ico":   "image/x-icon",
		".woff":  "font/woff",
		".woff2": "font/woff2",
		".ttf":   "font/ttf",
		".eot":   "application/vnd.ms-fontobject",
	}

	if ct, ok := types[strings.ToLower(ext)]; ok {
		return ct
	}
	return "application/octet-stream"
}
