package pkg_test

import (
	"crypto/sha512"
	"io"
	"io/fs"
	"net/http"
	"reflect"
	"testing"
	"testing/fstest"
	"unique"

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

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

	const testdata = "\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69"

	testdataChecksum := func() unique.Handle[pkg.Checksum] {
		h := sha512.New384()
		h.Write([]byte(testdata))
		return unique.Make(pkg.Checksum(h.Sum(nil)))
	}()

	var transport http.Transport
	client := http.Client{Transport: &transport}
	transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
		"testdata": {Data: []byte(testdata), Mode: 0400},
	}))

	checkWithCache(t, []cacheTestCase{
		{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
			r := newRContext(t, c)
			f := pkg.NewHTTPGet(
				&client,
				"file:///testdata",
				testdataChecksum.Value(),
			)
			var got []byte
			if rc, err := f.Cure(r); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if got, err = io.ReadAll(rc); err != nil {
				t.Fatalf("ReadAll: error = %v", err)
			} else if string(got) != testdata {
				t.Fatalf("Cure: %x, want %x", got, testdata)
			} else if err = rc.Close(); err != nil {
				t.Fatalf("Close: error = %v", err)
			}

			// check direct validation
			f = pkg.NewHTTPGet(
				&client,
				"file:///testdata",
				pkg.Checksum{},
			)
			wantErrMismatch := &pkg.ChecksumMismatchError{
				Got: testdataChecksum.Value(),
			}
			if rc, err := f.Cure(r); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if got, err = io.ReadAll(rc); err != nil {
				t.Fatalf("ReadAll: error = %v", err)
			} else if string(got) != testdata {
				t.Fatalf("Cure: %x, want %x", got, testdata)
			} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
				t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
			}

			// check fallback validation
			if rc, err := f.Cure(r); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
				t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
			}

			// check direct response error
			f = pkg.NewHTTPGet(
				&client,
				"file:///nonexistent",
				pkg.Checksum{},
			)
			wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
			if _, err := f.Cure(r); !reflect.DeepEqual(err, wantErrNotFound) {
				t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
			}
		}, expectsFS{
			".":          {Mode: fs.ModeDir | 0700},
			"checksum":   {Mode: fs.ModeDir | 0700},
			"identifier": {Mode: fs.ModeDir | 0700},
			"substitute": {Mode: fs.ModeDir | 0700},
			"work":       {Mode: fs.ModeDir | 0700},
		}},

		{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
			r := newRContext(t, c)

			f := pkg.NewHTTPGet(
				&client,
				"file:///testdata",
				testdataChecksum.Value(),
			)
			wantPathname := base.Append(
				"identifier",
				"COBRUwkxpIvZWJggpRxn7HVXiw6epXZOex8jGRJKpowpwukV7GRBY_LBvOJNQHue",
			)
			if pathname, checksum, err := c.Cure(f); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if !pathname.Is(wantPathname) {
				t.Fatalf("Cure: %q, want %q", pathname, wantPathname)
			} else if checksum != testdataChecksum {
				t.Fatalf("Cure: %x, want %x", checksum.Value(), testdataChecksum.Value())
			}

			var got []byte
			if rc, err := f.Cure(r); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if got, err = io.ReadAll(rc); err != nil {
				t.Fatalf("ReadAll: error = %v", err)
			} else if string(got) != testdata {
				t.Fatalf("Cure: %x, want %x", got, testdata)
			} else if err = rc.Close(); err != nil {
				t.Fatalf("Close: error = %v", err)
			}

			// check load from cache
			f = pkg.NewHTTPGet(
				&client,
				"file:///testdata",
				testdataChecksum.Value(),
			)
			if rc, err := f.Cure(r); err != nil {
				t.Fatalf("Cure: error = %v", err)
			} else if got, err = io.ReadAll(rc); err != nil {
				t.Fatalf("ReadAll: error = %v", err)
			} else if string(got) != testdata {
				t.Fatalf("Cure: %x, want %x", got, testdata)
			} else if err = rc.Close(); err != nil {
				t.Fatalf("Close: error = %v", err)
			}

			// check error passthrough
			f = pkg.NewHTTPGet(
				&client,
				"file:///nonexistent",
				pkg.Checksum{},
			)
			wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
			if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
				t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
			}
		}, expectsFS{
			".": {Mode: fs.ModeDir | 0700},

			"checksum": {Mode: fs.ModeDir | 0700},
			"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},

			"identifier": {Mode: fs.ModeDir | 0700},
			"identifier/COBRUwkxpIvZWJggpRxn7HVXiw6epXZOex8jGRJKpowpwukV7GRBY_LBvOJNQHue": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},

			"substitute": {Mode: fs.ModeDir | 0700},

			"work": {Mode: fs.ModeDir | 0700},
		}},
	})
}
