package caddyhugo

import (
	"fmt"
	"html/template"
	"net/http"
	"path"
	"path/filepath"
	"strings"
	"sync"

	"git.stephensearles.com/stephen/acedoc"
	"git.stephensearles.com/stephen/caddy-hugo2/comments"
	"git.stephensearles.com/stephen/caddy-hugo2/media"
	"go.uber.org/zap"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/gohugoio/hugo/deps"
	"github.com/gohugoio/hugo/hugolib"
	"github.com/spf13/afero"
)

func init() {
	caddy.RegisterModule(&CaddyHugo{})
	httpcaddyfile.RegisterHandlerDirective("hugo", parseCaddyfile)
}

type SiteConfig struct {
	Hosts            []string
	Root             string
	CommentsEnabled  bool
	CommentsPassword string
}

func (m *CaddyHugo) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.handlers.hugo",
		New: func() caddy.Module { return new(CaddyHugo) },
	}
}

func (m *CaddyHugo) Provision(ctx caddy.Context) error {
	m.logger = ctx.Logger(m)

	if m.Site.CommentsEnabled {
		m.Comments = comments.WithStorage(comments.NewDiskv(path.Join(m.Site.Root, "comments")))
	}

	// TODO: not sure where m.Site is getting populated.
	// m.Site.Root looks to be the working directory of caddy but it needs to be the directory of the site
	root, err := filepath.Abs(m.Site.Root)
	if err != nil {
		return err
	}

	err = m.Setup(root)
	if err != nil {
		return err
	}

	ctx.OnCancel(func() {
		m.persistAllEdits()
	})

	m.commentsSetting()
	return nil
}

// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (s *SiteConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	for d.NextBlock(0) {
		if d.Val() == "comments" {
			if d.NextArg() {
				s.CommentsPassword = d.Val()
			}
		}
	}
	return nil
}

// CaddyHugo implements the plugin for a single site
type CaddyHugo struct {
	logger *zap.Logger

	ServerType string
	Site       SiteConfig
	HugoSites  *hugolib.HugoSites
	HugoCfg    *deps.DepsCfg

	Dir string

	Media    *media.MediaSource
	Comments *comments.Service

	docs map[string]*editSession
	mtx  sync.Mutex

	authorTmpl, adminTmpl, editTmpl *template.Template

	ltime              uint64
	confirmingToClient map[uint64]struct{}
}

func (ch *CaddyHugo) log(msg string, args ...interface{}) {
	all := make([]any, len(args)+1)
	all[0] = msg
	copy(all[1:], args)
	ch.logger.Info(fmt.Sprint(all...))
}

func (ch *CaddyHugo) logf(msg string, args ...interface{}) {
	ch.logger.Info(fmt.Sprintf(msg, args...))
}

// Build rebuilds the cached state of the site. TODO: determine if this republishes
func (ch *CaddyHugo) Build() error {
	return buildSite(ch.HugoSites)
}

// BasePath returns the directory that the CaddyHugo internal/author pages are under
func (ch *CaddyHugo) BasePath() string {
	return "/hugo"
}

func docname(orig string) string {
	orig = strings.Replace(orig, " ", "-", -1)
	return strings.ToLower(orig)
}

func (ch *CaddyHugo) docFilename(orig string) string {
	return filepath.Join(ch.Dir, docname(orig))
}

// TmplData collects data for template execution
func (ch *CaddyHugo) TmplData(r *http.Request, docref *editSession) interface{} {
	var doc *acedoc.Document
	if docref != nil {
		doc = docref.doc
	}
	if ch.HugoSites != nil && ch.HugoSites.Fs != nil {
		ch.HugoSites.Cfg.Set("buildDrafts", true)
		ch.HugoSites.Fs.Destination = afero.NewMemMapFs()
		ch.Build()
	}
	return &tmplData{ch.Site, r, ch, doc, docref}
}

// parseCaddyfile unmarshals tokens from h into a new Middleware.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	var m CaddyHugo
	err := m.Site.UnmarshalCaddyfile(h.Dispenser)
	return &m, err
}

var (
	_ caddy.Provisioner = (*CaddyHugo)(nil)
)
