package rosa

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"runtime/debug"
	"strconv"
	"sync"
	"syscall"
	"unique"
	"unsafe"

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

// wordSize is the boundary which binary segments are always aligned to.
const wordSize = 8

// padSize returns the padding size for aligning sz.
func padSize[T int | int64](sz T) T {
	return (wordSize - (sz)%wordSize) % wordSize
}

// countWriter records total amount of data written.
type countWriter uint64

// Write records the size of p.
func (w *countWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	*w += countWriter(n)
	return
}

// WriteReport writes a report of all available [Artifact] to w.
func WriteReport(msg message.Msg, w io.Writer, c *pkg.Cache) error {
	var (
		zero [wordSize]byte
		buf  [len(pkg.ID{}) + wordSize]byte
	)
	t := builtin.Std()
	for _, p := range builtin.Collect() {
		meta, a := t.MustLoad(p)
		if meta == nil {
			return errors.New("artifact " + p.String() + " in inconsistent state")
		}
		if _, ok := a.(pkg.FileArtifact); ok {
			msg.Verbosef("skipping file artifact %s", meta.Name)
			continue
		}

		id := c.Ident(a)

		var f *os.File
		if r, err := c.OpenStatus(a); err != nil {
			if errors.Is(err, os.ErrNotExist) {
				msg.Verbosef("artifact %s unavailable", meta.Name)
				continue
			}
			return err
		} else {
			f = r.(*os.File)
		}

		msg.Verbosef("writing artifact %s...", meta.Name)

		var sz int64
		if fi, err := f.Stat(); err != nil {
			_ = f.Close()
			return err
		} else {
			sz = fi.Size()
		}

		*(*pkg.ID)(buf[:]) = id.Value()
		binary.LittleEndian.PutUint64(buf[len(pkg.ID{}):], uint64(sz))
		if _, err := w.Write(buf[:]); err != nil {
			_ = f.Close()
			return err
		}

		if n, err := io.Copy(w, f); err != nil {
			_ = f.Close()
			return err
		} else if n != sz {
			_ = f.Close()
			return fmt.Errorf("strange status file copy: %d != %d", n, sz)
		} else if err = f.Close(); err != nil {
			return err
		}

		if psz := padSize(sz); psz > 0 {
			if _, err := w.Write(zero[:psz]); err != nil {
				return err
			}
		}

		// existence of status implies cured artifact
		var n countWriter
		if pathname, _, err := c.Cure(a); err != nil {
			return err
		} else if err = pkg.Write(
			os.DirFS(pathname.String()), ".",
			&n,
		); err != nil {
			return err
		}
		binary.LittleEndian.PutUint64(buf[:], uint64(n))
		if _, err := w.Write(buf[:wordSize]); err != nil {
			return err
		}
	}
	return nil
}

// Report provides efficient access to a report file populated by [WriteReport].
type Report struct {
	// Slice backed by the underlying file.
	//
	// Access must be prepared by HandleAccess.
	data []byte

	// Offsets into data for each identifier.
	offsets map[unique.Handle[pkg.ID]]int

	// Outcome of a call to Close.
	closeErr error
	// Synchronises calls to Close.
	closeOnce sync.Once
}

// OpenReport opens a file populated by [WriteReport]
func OpenReport(pathname string) (rp *Report, err error) {
	var f *os.File
	if f, err = os.Open(pathname); err != nil {
		return
	}

	var fi os.FileInfo
	if fi, err = f.Stat(); err != nil {
		_ = f.Close()
		return
	}

	var r Report
	if r.data, err = syscall.Mmap(
		int(f.Fd()),
		0,
		int(fi.Size()),
		syscall.PROT_READ,
		syscall.MAP_PRIVATE,
	); err != nil {
		_ = f.Close()
		return
	}

	if err = f.Close(); err != nil {
		_ = r.Close()
		return
	}

	defer r.HandleAccess(&err)()

	var offset int
	r.offsets = make(map[unique.Handle[pkg.ID]]int)
	for offset < len(r.data) {
		id := unique.Make((pkg.ID)(r.data[offset:]))
		offset += len(pkg.ID{})
		r.offsets[id] = offset
		offset += int(binary.LittleEndian.Uint64(r.data[offset:])) + wordSize
		offset += padSize(offset)
		offset += wordSize
	}
	return &r, nil
}

// ReportIOError describes an I/O error while accessing a [Report].
type ReportIOError struct {
	Offset int
	Err    error
}

// Unwrap returns the underlying runtime error.
func (e *ReportIOError) Unwrap() error { return e.Err }

// Error returns a description of the error offset.
func (e *ReportIOError) Error() string {
	return "report I/O error at offset " + strconv.Itoa(e.Offset)
}

// HandleAccess prepares for accessing memory returned by a method of [Report]
// and returns a function that must be deferred by the caller.
func (r *Report) HandleAccess(errP *error) func() {
	pof := debug.SetPanicOnFault(true)
	return func() {
		debug.SetPanicOnFault(pof)

		v := recover()
		if v == nil {
			return
		}

		if err, ok := v.(error); !ok {
			panic(v)
		} else if *errP != nil {
			return
		} else {
			*errP = err
		}

		if runtimeError, ok := errors.AsType[interface {
			Addr() uintptr
			runtime.Error
		}](*errP); ok {
			offset := int(runtimeError.Addr() - uintptr(unsafe.Pointer(unsafe.SliceData(r.data))))
			// best effort for fragile uintptr
			if offset >= 0 {
				*errP = &ReportIOError{offset, *errP}
			}
		}
	}
}

// ArtifactOf returns information of a [pkg.Artifact] corresponding to id.
func (r *Report) ArtifactOf(id unique.Handle[pkg.ID]) (status []byte, n int64) {
	if offset, ok := r.offsets[id]; !ok {
		n = -1
	} else {
		sz := int(binary.LittleEndian.Uint64(r.data[offset:]))
		offset += wordSize

		status = r.data[offset : offset+sz]
		offset += sz + padSize(sz)

		n = int64(binary.LittleEndian.Uint64(r.data[offset:]))
	}
	return
}

// Close closes the underlying file and releases all associated resources.
func (r *Report) Close() error {
	r.closeOnce.Do(func() { r.closeErr = syscall.Munmap(r.data) })
	return r.closeErr
}
