package chunked

import (
	"bytes"
	"fmt"
	"io"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	graphdriver "github.com/containers/storage/drivers"
)

const jsonTOC = `
{
  "version": 1,
  "entries": [
    {
      "type": "symlink",
      "name": "bin",
      "linkName": "usr/bin",
      "mode": 511,
      "modtime": "1970-01-01T01:00:00+01:00",
      "accesstime": "0001-01-01T00:00:00Z",
      "changetime": "0001-01-01T00:00:00Z"
    },
    {
      "type": "dir",
      "name": "usr/bin",
      "mode": 511,
      "modtime": "2022-01-07T12:36:43+01:00",
      "accesstime": "0001-01-01T00:00:00Z",
      "changetime": "0001-01-01T00:00:00Z"
    },
    {
      "type": "reg",
      "name": "usr/bin/foo",
      "mode": 511,
      "size": 103867,
      "modtime": "1970-01-01T01:00:00+01:00",
      "accesstime": "0001-01-01T00:00:00Z",
      "changetime": "0001-01-01T00:00:00Z",
      "digest": "sha256:99fe908c699dc068438b23e28319cadff1f2153c3043bafb8e83a430bba0a2c6",
      "offset": 94149,
      "endOffset": 120135,
      "chunkSize": 17615,
      "chunkDigest": "sha256:2ce0d0f8eb2aa93d13007097763e4459c814c8d0e859e5a57465af924169b544"
    },
    {
      "type": "chunk",
      "name": "usr/bin/foo",
      "offset": 99939,
      "chunkSize": 86252,
      "chunkOffset": 17615,
      "chunkDigest": "sha256:2a9d3f1b6b37abc8bb35eb8fa98b893a2a2447bcb01184c3bafc8c6b40da099d"
    }
}
`

func TestPrepareMetadata(t *testing.T) {
	toc, err := prepareMetadata([]byte(jsonTOC), graphdriver.DifferOutputFormatDir)
	if err != nil {
		t.Errorf("got error from prepareMetadata: %v", err)
	}
	if len(toc) != 2 {
		t.Error("prepareMetadata returns the wrong length")
	}
}

func TestPrepareMetadataFlat(t *testing.T) {
	toc, err := prepareMetadata([]byte(jsonTOC), graphdriver.DifferOutputFormatFlat)
	if err != nil {
		t.Errorf("got error from prepareMetadata: %v", err)
	}
	for _, e := range toc {
		if len(strings.Split(e.Name, "/")) != 2 {
			t.Error("prepareMetadata returns the wrong number of path elements for flat directories")
		}
		if len(filepath.Dir(e.Name)) != 2 {
			t.Error("prepareMetadata returns the wrong path for flat directories")
		}
	}
}

type bigDataToBuffer struct {
	buf    *bytes.Buffer
	id     string
	key    string
	called bool
}

func (b *bigDataToBuffer) SetLayerBigData(id, key string, data io.Reader) error {
	b.id = id
	b.key = key
	if b.called {
		return fmt.Errorf("SetLayerBigData already called once")
	}
	b.called = true
	_, err := io.Copy(b.buf, data)
	return err
}

func TestWriteCache(t *testing.T) {
	toc, err := prepareMetadata([]byte(jsonTOC), graphdriver.DifferOutputFormatDir)
	if err != nil {
		t.Errorf("got error from prepareMetadata: %v", err)
	}

	dest := bigDataToBuffer{
		buf: bytes.NewBuffer(nil),
	}
	cache, err := writeCache([]byte(jsonTOC), graphdriver.DifferOutputFormatDir, "foobar", &dest)
	if err != nil {
		t.Errorf("got error from writeCache: %v", err)
	}
	if digest, _, _ := findTag("foobar", cache); digest != "" {
		t.Error("found invalid tag")
	}

	for _, r := range toc {
		if r.Digest != "" {
			// find the element in the cache by the digest checksum
			digest, off, len := findTag(r.Digest, cache)
			if digest == "" {
				t.Error("file tag not found")
			}
			if digest != r.Digest {
				t.Error("wrong file found")
			}
			expectedLocation := generateFileLocation(r.Name, 0)
			location := cache.vdata[off : off+len]
			if !bytes.Equal(location, expectedLocation) {
				t.Errorf("wrong file found %q instead of %q", location, expectedLocation)
			}

			fingerprint, err := calculateHardLinkFingerprint(r)
			if err != nil {
				t.Errorf("got error from writeCache: %v", err)
			}

			// find the element in the cache by the hardlink fingerprint
			digest, off, len = findTag(fingerprint, cache)
			if digest == "" {
				t.Error("file tag not found")
			}
			if digest != fingerprint {
				t.Error("wrong file found")
			}
			expectedLocation = generateFileLocation(r.Name, 0)
			location = cache.vdata[off : off+len]
			if !bytes.Equal(location, expectedLocation) {
				t.Errorf("wrong file found %q instead of %q", location, expectedLocation)
			}
		}
		if r.ChunkDigest != "" {
			// find the element in the cache by the chunk digest checksum
			digest, off, len := findTag(r.ChunkDigest, cache)
			if digest == "" {
				t.Error("chunk tag not found")
			}
			if digest != r.ChunkDigest {
				t.Error("wrong digest found")
			}
			expectedLocation := generateFileLocation(r.Name, uint64(r.ChunkOffset))
			location := cache.vdata[off : off+len]
			if !bytes.Equal(location, expectedLocation) {
				t.Errorf("wrong file found %q instead of %q", location, expectedLocation)
			}
		}
	}
}

func TestReadCache(t *testing.T) {
	dest := bigDataToBuffer{
		buf: bytes.NewBuffer(nil),
	}
	cache, err := writeCache([]byte(jsonTOC), graphdriver.DifferOutputFormatDir, "foobar", &dest)
	if err != nil {
		t.Errorf("got error from writeCache: %v", err)
	}

	cacheRead, err := readMetadataFromCache(dest.buf)
	if err != nil {
		t.Errorf("got error from readMetadataFromCache: %v", err)
	}
	if !reflect.DeepEqual(cache, cacheRead) {
		t.Errorf("read a different struct than what was written")
	}
}
