package web

import (
	"net/url"
	"path"
	"strings"

	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
)

// ReadmeContext contains information needed to rewrite URLs in README files.
type ReadmeContext struct {
	RepoName   string // Repository name (e.g., "myrepo")
	CommitHash string // Full commit hash to pin URLs to
	ReadmePath string // Path to README file (e.g., "docs/README.md")
}

// urlRewriter is a Goldmark AST transformer that rewrites relative URLs in markdown.
type urlRewriter struct {
	ctx ReadmeContext
}

// newURLRewriter creates a new URL rewriter transformer.
func newURLRewriter(ctx ReadmeContext) *urlRewriter {
	return &urlRewriter{ctx: ctx}
}

// Transform implements ast.Transformer.
func (r *urlRewriter) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
	// Get the directory containing the README
	readmeDir := path.Dir(r.ctx.ReadmePath)
	if readmeDir == "." {
		readmeDir = ""
	}

	// Walk the AST and rewrite links and images
	ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
		if !entering {
			return ast.WalkContinue, nil
		}

		switch v := n.(type) {
		case *ast.Link:
			v.Destination = r.rewriteURL(v.Destination, readmeDir, false)
		case *ast.Image:
			v.Destination = r.rewriteURL(v.Destination, readmeDir, true)
		}

		return ast.WalkContinue, nil
	})
}

// rewriteURL rewrites a single URL destination.
func (r *urlRewriter) rewriteURL(dest []byte, readmeDir string, isImage bool) []byte {
	destStr := string(dest)

	// Skip empty URLs
	if destStr == "" {
		return dest
	}

	// Parse the URL
	u, err := url.Parse(destStr)
	if err != nil {
		// Invalid URL, fail closed by returning empty
		return []byte("")
	}

	// Skip absolute URLs (http://, https://, mailto:, etc.)
	if u.Scheme != "" {
		return dest
	}

	// Skip protocol-relative URLs (//example.com/path)
	// These have no scheme but do have a host
	if u.Host != "" {
		return []byte("")
	}

	// Skip anchor-only links (#section)
	if strings.HasPrefix(destStr, "#") {
		return dest
	}

	// Skip absolute paths (starting with /)
	if strings.HasPrefix(destStr, "/") {
		return dest
	}

	// Now we have a relative URL - resolve it against the README directory
	// Join README dir with the relative path
	var resolvedPath string
	if readmeDir != "" {
		resolvedPath = path.Join(readmeDir, u.Path)
	} else {
		resolvedPath = u.Path
	}

	// Clean the path to resolve .. and .
	resolvedPath = path.Clean(resolvedPath)

	// Security check: reject any path that escapes the repository root
	// After path.Clean, a path starting with ../ indicates traversal outside the repo
	if strings.HasPrefix(resolvedPath, "../") || resolvedPath == ".." {
		// Path tries to escape repo, return empty/invalid
		return []byte("")
	}

	// For images, always rewrite to blob endpoint with ?raw=1
	// For links, check if it's a file (has extension) or could be a directory/markdown file
	if isImage {
		// Build URL: /{repo}/blob/{commit}/{path}?raw=1
		rewritten := "/" + r.ctx.RepoName + "/blob/" + r.ctx.CommitHash + "/" + resolvedPath + "?raw=1"
		// Preserve fragment if present
		if u.Fragment != "" {
			rewritten += "#" + u.Fragment
		}
		return []byte(rewritten)
	}

	// For links, determine if it's a file or navigation
	ext := path.Ext(resolvedPath)
	if ext != "" && ext != ".md" && ext != ".markdown" {
		// It's a file (not markdown), serve raw
		rewritten := "/" + r.ctx.RepoName + "/blob/" + r.ctx.CommitHash + "/" + resolvedPath + "?raw=1"
		if u.Fragment != "" {
			rewritten += "#" + u.Fragment
		}
		return []byte(rewritten)
	} else if ext == ".md" || ext == ".markdown" {
		// It's a markdown file, link to blob view (rendered)
		rewritten := "/" + r.ctx.RepoName + "/blob/" + r.ctx.CommitHash + "/" + resolvedPath
		if u.Fragment != "" {
			rewritten += "#" + u.Fragment
		}
		return []byte(rewritten)
	} else {
		// No extension, could be a directory - link to tree view
		rewritten := "/" + r.ctx.RepoName + "/tree/" + r.ctx.CommitHash + "/" + resolvedPath
		if u.Fragment != "" {
			rewritten += "#" + u.Fragment
		}
		return []byte(rewritten)
	}
}
