package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"testing"
	"unique"
	"unsafe"

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

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

	_t := rosa.Native().Std()
	qemuMeta, _ := _t.Load(rosa.H("qemu"))
	glibMeta, _ := _t.Load(rosa.H("glib"))
	zlibMeta, zlib := _t.Load(rosa.H("zlib"))
	zstdMeta, _ := _t.Load(rosa.H("zstd"))
	hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
	hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))

	testCases := []struct {
		name    string
		args    []string
		status  map[string]string
		report  string
		want    string
		wantErr any
	}{
		{"qemu", []string{"qemu"}, nil, "", `
name        : qemu-` + qemuMeta.Version + `
description : a generic and open source machine emulator and virtualizer
website     : https://www.qemu.org
depends on  : glib-` + glibMeta.Version + ` zstd-` + zstdMeta.Version + `
`, nil},

		{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
name        : hakurei-` + hakureiMeta.Version + `
description : low-level userspace tooling for Rosa OS
website     : https://hakurei.app

name        : hakurei-dist-` + hakureiDistMeta.Version + `
description : low-level userspace tooling for Rosa OS (distribution tarball)
website     : https://hakurei.app
`, nil},

		{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
name        : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website     : https://zlib.net

`, fmt.Errorf("unknown artifact %q", "\x00")},

		{"status cache", []string{"zlib", "zstd"}, map[string]string{
			"zstd":    "internal/pkg (amd64) on satori\n",
			"hakurei": "internal/pkg (amd64) on satori\n\n",
		}, "", `
name        : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website     : https://zlib.net
status      : not yet cured

name        : zstd-` + zstdMeta.Version + `
description : a fast compression algorithm
website     : https://facebook.github.io/zstd
status      : internal/pkg (amd64) on satori
`, nil},

		{"status cache perm", []string{"zlib"}, map[string]string{
			"zlib": "\x00",
		}, "", `
name        : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website     : https://zlib.net
`, func(cm *cache) error {
			return &os.PathError{
				Op:   "open",
				Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
				Err:  syscall.EACCES,
			}
		}},

		{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
name        : zlib-` + zlibMeta.Version + `
description : lossless data-compression library
website     : https://zlib.net
status      : not in report
`, nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			var (
				cm  *cache
				buf strings.Builder
				r   *rosa.Report
			)

			if tc.status != nil || tc.report != "" {
				cm = &cache{
					ctx:  context.Background(),
					msg:  message.New(log.New(os.Stderr, "info: ", 0)),
					base: t.TempDir(),
				}
				defer cm.Close()
			}

			if tc.report != "" {
				pathname := filepath.Join(t.TempDir(), "report")
				err := os.WriteFile(
					pathname,
					unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
					0400,
				)
				if err != nil {
					t.Fatal(err)
				}

				r, err = rosa.OpenReport(pathname)
				if err != nil {
					t.Fatal(err)
				}
				defer func() {
					if err = r.Close(); err != nil {
						t.Fatal(err)
					}
				}()
			}

			if tc.status != nil {
				for name, status := range tc.status {
					_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
					if a == nil {
						t.Fatalf("invalid name %q", name)
					}
					perm := os.FileMode(0400)
					if status == "\x00" {
						perm = 0
					}
					if err := cm.Do(func(cache *pkg.Cache) error {
						return os.WriteFile(filepath.Join(
							cm.base,
							"status",
							pkg.Encode(cache.Ident(a).Value()),
						), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
					}); err != nil {
						t.Fatalf("Do: error = %v", err)
					}
				}
			}

			var wantErr error
			switch c := tc.wantErr.(type) {
			case error:
				wantErr = c
			case func(cm *cache) error:
				wantErr = c(cm)
			default:
				if tc.wantErr != nil {
					t.Fatalf("invalid wantErr %#v", tc.wantErr)
				}
			}

			if err := commandInfo(
				cm,
				tc.args,
				&buf,
				cm != nil,
				r,
			); !reflect.DeepEqual(err, wantErr) {
				t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
			}

			if got := buf.String(); got != strings.TrimPrefix(tc.want, "\n") {
				t.Errorf("commandInfo:\n%s\nwant\n%s", got, tc.want)
			}
		})
	}
}
