package sync

import (
	"bytes"
	"encoding/base32"
	"io"
	"os"
	"path"
	"strings"

	"srs.epita.fr/fic-server/libfic"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
	"golang.org/x/crypto/blake2b"
)

func ProcessMarkdown(i Importer, input string, rootDir string) (output string, err error) {
	// Define the path where save linked files
	hash := blake2b.Sum512([]byte(rootDir))

	imgImporter := NewImageImporterTransformer(i, rootDir, hash)

	// Process md
	markdown := goldmark.New(
		goldmark.WithExtensions(extension.DefinitionList),
		goldmark.WithExtensions(extension.Linkify),
		goldmark.WithExtensions(extension.Strikethrough),
		goldmark.WithExtensions(extension.Table),
		goldmark.WithExtensions(extension.Typographer),
		goldmark.WithParserOptions(
			parser.WithASTTransformers(
				util.Prioritized(imgImporter, 200),
			),
		),
		goldmark.WithRendererOptions(
			html.WithHardWraps(),
		),
	)

	var buf bytes.Buffer
	context := parser.NewContext()
	if err = markdown.Convert([]byte(input), &buf, parser.WithContext(context)); err != nil {
		return
	}

	output = string(buf.Bytes())

	// Trim output
	output = strings.TrimSpace(output)

	return output, imgImporter.(*imageImporterTransformer).err
}

type imageImporterTransformer struct {
	importer Importer
	rootDir  string
	hash     [blake2b.Size]byte
	absPath  string
	err      error
}

func NewImageImporterTransformer(i Importer, rootDir string, hash [blake2b.Size]byte) parser.ASTTransformer {
	absPath := "$FILES$/" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:]))
	return &imageImporterTransformer{i, rootDir, hash, absPath, nil}
}

func (t *imageImporterTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
	t.err = ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
		if !enter {
			return ast.WalkContinue, nil
		}

		switch child := node.(type) {
		case *ast.Image:
			iPath := string(child.Destination)
			dPath := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(t.hash[:])), iPath)
			child.Destination = []byte(path.Join(t.absPath, string(child.Destination)))

			if err := os.MkdirAll(path.Dir(dPath), 0755); err != nil {
				return ast.WalkStop, err
			}

			if fdto, err := os.Create(dPath); err != nil {
				return ast.WalkStop, err
			} else {
				defer fdto.Close()

				if fd, closer, err := GetFile(t.importer, path.Join(t.rootDir, iPath)); err != nil {
					os.Remove(dPath)
					return ast.WalkStop, err
				} else {
					defer closer()

					_, err = io.Copy(fdto, fd)
					if err != nil {
						return ast.WalkStop, err
					}
				}
			}
		}

		return ast.WalkContinue, nil
	})
}
