package main

import (
	"bytes"
	"context"
	"errors"
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"
	"time"

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

func TestNoReply(t *testing.T) {
	t.Parallel()
	if !daemonDeadline().IsZero() {
		t.Fatal("daemonDeadline did not return the zero value")
	}

	c, err := pkg.Open(
		t.Context(),
		message.New(log.New(os.Stderr, "cir: ", 0)),
		check.MustAbs(t.TempDir()),
		nil,
	)
	if err != nil {
		t.Fatalf("Open: error = %v", err)
	}
	defer c.Close()

	client, server := net.Pipe()
	done := make(chan struct{})
	go func() {
		defer close(done)
		go func() {
			<-t.Context().Done()
			if _err := client.SetDeadline(time.Now()); _err != nil && !errors.Is(_err, io.ErrClosedPipe) {
				panic(_err)
			}
		}()

		if _err := c.EncodeAll(
			client,
			pkg.NewFile("check", []byte{0}),
		); _err != nil {
			panic(_err)
		} else if _err = client.Close(); _err != nil {
			panic(_err)
		}
	}()

	a, cureErr := cureFromIR(c, server, remoteNoReply)
	if cureErr != nil {
		t.Fatalf("cureFromIR: error = %v", cureErr)
	}

	<-done
	wantIdent := pkg.MustDecode("bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32")
	if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
		t.Errorf(
			"cureFromIR: %s, want %s",
			pkg.Encode(gotIdent), pkg.Encode(wantIdent),
		)
	}
}

func TestDaemon(t *testing.T) {
	t.Parallel()

	var buf bytes.Buffer
	logger := log.New(&buf, "daemon: ", 0)

	addr := net.UnixAddr{
		Name: filepath.Join(t.TempDir(), "daemon"),
		Net:  "unix",
	}

	ctx, cancel := context.WithCancel(t.Context())
	defer cancel()

	cm := cache{
		ctx:  ctx,
		msg:  message.New(logger),
		base: t.TempDir(),
	}
	defer cm.Close()

	ul, err := net.ListenUnix("unix", &addr)
	if err != nil {
		t.Fatalf("ListenUnix: error = %v", err)
	}

	done := make(chan struct{})
	go func() {
		defer close(done)
		if _err := serve(ctx, logger, &cm, ul); _err != nil {
			panic(_err)
		}
	}()

	if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil), true); err != nil {
		t.Fatalf("cancelRemote: error = %v", err)
	}

	if err = abortRemote(ctx, &addr, true); err != nil {
		t.Fatalf("abortRemote: error = %v", err)
	}

	// keep this last for synchronisation
	var p *check.Absolute
	p, err = cureRemote(ctx, &addr, pkg.NewFile("check", []byte{0}), 0)
	if err != nil {
		t.Fatalf("cureRemote: error = %v", err)
	}

	cancel()
	<-done

	const want = "bpVsJ69hdKPBntMh_u777g7-quFNVmMlBJQOlM_thiQa9JnOFLAbe5OlR4zNjZ32"
	if got := filepath.Base(p.String()); got != want {
		t.Errorf("cureRemote: %s, want %s", got, want)
	}

	wantLog := []string{
		"",
		"daemon: aborting all pending cures",
		"daemon: attempting to cancel invalid artifact 58Vy_5beLZCvZX__KzRKyIE6vATleaZP1ZBxbqoWnekz9aZ1zkxNgcIUFI5V1_Jf",
		"daemon: fulfilled artifact " + want,
	}
	gotLog := strings.Split(buf.String(), "\n")
	slices.Sort(gotLog)
	if !slices.Equal(gotLog, wantLog) {
		t.Errorf(
			"serve: logged\n%s\nwant\n%s",
			strings.Join(gotLog, "\n"), strings.Join(wantLog, "\n"),
		)
	}
}
