package main

import (
	"errors"
	"log"
	"os"
	"strconv"
	"strings"

	"hakurei.app/check"
	"hakurei.app/fhs"
	"hakurei.app/hst"
	"hakurei.app/internal/env"
	"hakurei.app/internal/lockedfile"
	"hakurei.app/internal/outcome"
)

// MutationConflictError describes an active mutable instance.
type MutationConflictError string

func (e MutationConflictError) Error() string {
	return "mutable instance active at " + string(e)
}

// informTemplate guards intention of a template or its derivatives.
func informTemplate(base *check.Absolute, name string, mutable bool) (func() error, error) {
	mu := lockedfile.MutexAt(base.Append("lock", name).String())
	if unlock, err := mu.Lock(); err != nil {
		return nil, err
	} else {
		defer unlock()
	}

	marker := base.Append("lock", "."+name)
	if p, err := os.ReadFile(marker.String()); err == nil {
		if _, err = os.Stat(fhs.AbsProc.Append(string(p)).String()); err == nil {
			return nil, MutationConflictError(p)
		} else if !errors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		log.Printf("removing stale marker by %s", string(p))
		if err = os.Remove(marker.String()); err != nil {
			return nil, err
		}
	} else if !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}

	if !mutable {
		return nil, nil
	}

	var active []hst.ID
	var sc hst.Paths
	env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
	entries, copyError := outcome.NewStore(&sc).All()
	var s hst.State
	for eh := range entries {
		s = hst.State{}
		if _, err := eh.Load(&s); err != nil {
			return nil, err
		}

		if s.Validate(0) != nil || len(s.Container.Filesystem) < 1 {
			continue
		}
		root, ok := s.Container.Filesystem[0].FilesystemConfig.(*hst.FSOverlay)
		if !ok || root == nil {
			continue
		}

		if !root.Target.Is(fhs.AbsRoot) ||
			len(root.Lower) != 1 ||
			!root.Lower[0].Is(base.Append("initial")) ||
			!root.Upper.Is(base.Append("template", name)) ||
			root.Work != nil {
			continue
		}

		active = append(active, s.ID)
	}
	if err := copyError(); err != nil {
		return nil, err
	}

	if len(active) != 0 {
		var buf strings.Builder
		buf.WriteString("derivative instances still active:")
		for _, id := range active {
			buf.WriteString("\n\t")
			buf.WriteString(id.String())
		}
		return nil, errors.New(buf.String())
	}

	return func() error { return os.RemoveAll(marker.String()) }, os.WriteFile(
		marker.String(),
		[]byte(strconv.Itoa(os.Getpid())),
		0400,
	)
}

// acquireTemplate obtains exclusivity of a template.
func acquireTemplate(base *check.Absolute, name string) (remove func() error, err error) {
	return informTemplate(base, name, true)
}

// enterTemplate checks against exclusivity of a template.
func enterTemplate(base *check.Absolute, name string) error {
	_, err := informTemplate(base, name, false)
	return err
}
