package main

import (
	"context"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"testing"

	"hakurei.app/check"
	"hakurei.app/internal/pkg"
	"hakurei.app/internal/rosa"
	"hakurei.app/message"
)

// cache refers to an instance of [pkg.Cache] that might be open.
type cache struct {
	ctx context.Context
	msg message.Msg

	// Should generally not be used directly.
	c *pkg.Cache

	attr pkg.CacheAttr
	// Primarily to work around missing landlock LSM.
	hostAbstract bool
	// Set SCHED_IDLE.
	idle bool
	// Unset [pkg.CSuppressInit].
	verboseInit bool
	// Whether to enable output colours.
	color bool
	// Unset [pkg.CExternShallow].
	deep bool
	// Loaded artifact of [rosa.QEMU].
	qemu pkg.Artifact

	base, mirror string
}

// writeTitle sets title of the terminal connected to [message.Msg].
func writeTitle(msg message.Msg, s string) {
	msg.Suspend()
	w := msg.GetLogger().Writer()
	_, _ = w.Write([]byte("\x1b]0;" + s + "\a"))
	msg.Resume()
}

// open opens the underlying [pkg.Cache].
func (cache *cache) open() (err error) {
	if cache.c != nil {
		return os.ErrInvalid
	}

	var hostname string
	if hostname, err = os.Hostname(); err != nil {
		return
	}

	var base *check.Absolute
	if cache.base, err = filepath.Abs(cache.base); err != nil {
		return
	} else if base, err = check.NewAbs(cache.base); err != nil {
		return
	}
	var notify chan bool

	if cache.idle {
		cache.attr.Flags |= pkg.CSchedIdle
	}
	if cache.hostAbstract {
		cache.attr.Flags |= pkg.CHostAbstract
	}
	if !cache.verboseInit {
		cache.attr.Flags |= pkg.CSuppressInit
	}
	if !cache.deep {
		cache.attr.Flags |= pkg.CExternShallow
	}
	if cache.color {
		cache.attr.Flags |= pkg.CColourOutput
		notify = make(chan bool, 1<<12)
		cache.attr.Notify = notify
	}

	done := make(chan struct{})
	defer close(done)
	go func() {
		select {
		case <-cache.ctx.Done():
			if testing.Testing() {
				return
			}
			os.Exit(2)

		case <-done:
			return
		}
	}()

	cache.msg.Verbosef("opening cache at %s", base)
	cache.c, err = pkg.Open(
		cache.ctx,
		cache.msg,
		base,
		&cache.attr,
	)
	if err != nil {
		return
	}
	done <- struct{}{}

	if cache.mirror != "" {
		var pub []byte
		pub, err = os.ReadFile(base.Append("ed25519.pub").String())
		if err != nil {
			cache.c.Close()
			return
		}
		var r rosa.Remote
		if r, err = rosa.NewRemote(http.DefaultClient, cache.mirror, pub); err != nil {
			cache.c.Close()
			return err
		}
		cache.c.SetExternal(r)
	}

	if cache.attr.Notify != nil {
		cache.msg.Suspend()
		_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[22;0t"))
		cache.msg.Resume()

		prefix := hostname + ": mbf "
		go func() {
			var n, nc uint64
			for enter := range notify {
				if enter {
					nc++
				} else {
					nc--
					n++
				}

				if nc == 0 {
					writeTitle(cache.msg, prefix[:len(prefix)-1])
					continue
				}
				writeTitle(cache.msg, prefix+
					"("+strconv.FormatUint(nc, 10)+
					" running, "+strconv.FormatUint(n, 10)+" complete)")
			}
		}()
	}

	if cache.qemu != nil {
		var pathname *check.Absolute
		pathname, _, err = cache.c.Cure(cache.qemu)
		if err != nil {
			cache.c.Close()
			return
		}

		for arch, entry := range rosa.Arches(pathname) {
			pkg.RegisterArch(arch, entry)
		}
	}

	return
}

// Close closes the underlying [pkg.Cache] if it is open.
func (cache *cache) Close() {
	if cache.c != nil {
		cache.c.Close()
		if cache.attr.Notify != nil {
			cache.msg.Suspend()
			_, _ = cache.msg.GetLogger().Writer().Write([]byte("\x1b[23;0t"))
			cache.msg.Resume()
		}
	}
}

// Do calls f on the underlying cache and returns its error value.
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
	if cache.c == nil {
		if err := cache.open(); err != nil {
			return err
		}
	}
	return f(cache.c)
}
