package oci_test

import (
	"bytes"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/forgeo/forgebucket/internal/domain/oci"
)

func TestParseOCIPath(t *testing.T) {
	tests := []struct {
		path      string
		wantName  string
		wantKind  string
		wantRef   string
	}{
		{"/v2/", "", "", ""},
		{"/v2", "", "", ""},
		{"/v2/alice/myapp/tags/list", "alice/myapp", "tags", ""},
		{"/v2/alice/myapp/manifests/latest", "alice/myapp", "manifest", "latest"},
		{"/v2/alice/myapp/manifests/sha256:abc123", "alice/myapp", "manifest", "sha256:abc123"},
		{"/v2/alice/myapp/blobs/sha256:def456", "alice/myapp", "blob", "sha256:def456"},
		{"/v2/alice/myapp/blobs/uploads/", "alice/myapp", "upload", ""},
		{"/v2/alice/myapp/blobs/uploads/uuid123", "alice/myapp", "upload", "uuid123"},
	}

	for _, tt := range tests {
		t.Run(tt.path, func(t *testing.T) {
			name, kind, ref := oci.ParseOCIPath(tt.path)
			if name != tt.wantName {
				t.Errorf("name = %q, want %q", name, tt.wantName)
			}
			if kind != tt.wantKind {
				t.Errorf("kind = %q, want %q", kind, tt.wantKind)
			}
			if ref != tt.wantRef {
				t.Errorf("ref = %q, want %q", ref, tt.wantRef)
			}
		})
	}
}

func TestValidateName(t *testing.T) {
	if err := oci.ValidateName("alice/myapp"); err != nil {
		t.Errorf("valid name got error: %v", err)
	}
	if err := oci.ValidateName(""); err == nil {
		t.Error("empty name should error")
	}
	if err := oci.ValidateName("alice/my app"); err == nil {
		t.Error("name with spaces should error")
	}
}

func TestBlobPath(t *testing.T) {
	dir := t.TempDir()
	reg, err := oci.New(dir)
	if err != nil {
		t.Fatal(err)
	}

	p, err := reg.BlobPath("sha256:" + strings.Repeat("a", 64))
	if err != nil {
		t.Fatal(err)
	}

	expectedSuffix := filepath.Join("blobs", "sha256", strings.Repeat("a", 64))
	if !strings.HasSuffix(p, expectedSuffix) {
		t.Errorf("path %q does not end with %q", p, expectedSuffix)
	}

	if _, err := reg.BlobPath("sha256:bad"); err == nil {
		t.Error("expected error for short hex")
	}
	if _, err := reg.BlobPath("md5:abc"); err == nil {
		t.Error("expected error for non-sha256 algorithm")
	}
}

func TestWriteAndReadBlob(t *testing.T) {
	dir := t.TempDir()
	reg, err := oci.New(dir)
	if err != nil {
		t.Fatal(err)
	}

	content := []byte("hello oci blob")
	digest, size, err := reg.WriteBlob(bytes.NewReader(content))
	if err != nil {
		t.Fatalf("WriteBlob: %v", err)
	}

	if !strings.HasPrefix(digest, "sha256:") {
		t.Errorf("digest should start with sha256:, got %s", digest)
	}
	if size != int64(len(content)) {
		t.Errorf("size = %d, want %d", size, len(content))
	}

	if !reg.BlobExists(digest) {
		t.Error("blob should exist after write")
	}

	// Deduplication test: writing same content again should succeed without error.
	d2, s2, err := reg.WriteBlob(bytes.NewReader(content))
	if err != nil {
		t.Fatalf("WriteBlob duplicate: %v", err)
	}
	if d2 != digest {
		t.Errorf("digest mismatch: %s vs %s", d2, digest)
	}
	if s2 != size {
		t.Errorf("size mismatch: %d vs %d", s2, size)
	}

	f, err := reg.ReadBlob(digest)
	if err != nil {
		t.Fatalf("ReadBlob: %v", err)
	}
	defer f.Close()

	buf := new(bytes.Buffer)
	buf.ReadFrom(f)
	if buf.String() != string(content) {
		t.Errorf("content mismatch: got %s", buf.String())
	}
}

func TestUploadSession(t *testing.T) {
	dir := t.TempDir()
	reg, _ := oci.New(dir)

	uploadID := "test-upload-001"

	// Append content in chunks.
	off, err := reg.AppendUpload(uploadID, strings.NewReader("chunk1"))
	if err != nil {
		t.Fatalf("AppendUpload: %v", err)
	}
	if off != 6 {
		t.Errorf("expected offset 6, got %d", off)
	}

	off, err = reg.AppendUpload(uploadID, strings.NewReader("-chunk2"))
	if err != nil {
		t.Fatalf("AppendUpload second: %v", err)
	}
	if off != 13 {
		t.Errorf("expected offset 13 after chunk2, got %d", off)
	}

	if reg.UploadOffset(uploadID) != 13 {
		t.Errorf("UploadOffset = %d, want 13", reg.UploadOffset(uploadID))
	}

	// Finish upload with digest.
	digest, size, err := reg.FinishUpload(uploadID, "")
	if err != nil {
		t.Fatalf("FinishUpload: %v", err)
	}
	if !strings.HasPrefix(digest, "sha256:") {
		t.Errorf("expected sha256 digest, got %s", digest)
	}
	if size != 13 {
		t.Errorf("expected size 13, got %d", size)
	}

	if !reg.BlobExists(digest) {
		t.Error("blob should exist after finish upload")
	}

	// Verify content.
	f, _ := reg.ReadBlob(digest)
	buf := new(bytes.Buffer)
	buf.ReadFrom(f)
	f.Close()
	if buf.String() != "chunk1-chunk2" {
		t.Errorf("content = %q, want %q", buf.String(), "chunk1-chunk2")
	}
}

func TestFinishUploadDigestMismatch(t *testing.T) {
	dir := t.TempDir()
	reg, _ := oci.New(dir)

	uploadID := "mismatch-upload"
	reg.AppendUpload(uploadID, strings.NewReader("some data"))

	_, _, err := reg.FinishUpload(uploadID, "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
	if err == nil {
		t.Fatal("expected digest mismatch error")
	}
	if !strings.Contains(err.Error(), "digest mismatch") {
		t.Errorf("expected 'digest mismatch', got: %v", err)
	}
}

func TestManifestDescriptor(t *testing.T) {
	body := []byte(`{"schemaVersion":2}`)
	digest, size := oci.ManifestDescriptor(body)
	if !strings.HasPrefix(digest, "sha256:") {
		t.Errorf("digest should be sha256, got %s", digest)
	}
	if size != int64(len(body)) {
		t.Errorf("size = %d, want %d", size, len(body))
	}
}

func TestIsDigestRef(t *testing.T) {
	if !oci.IsDigestRef("sha256:abc") {
		t.Error("sha256:abc should be a digest ref")
	}
	if oci.IsDigestRef("latest") {
		t.Error("latest should NOT be a digest ref")
	}
}

func TestDeleteBlob(t *testing.T) {
	dir := t.TempDir()
	reg, _ := oci.New(dir)
	content := []byte("delete me")
	digest, _, _ := reg.WriteBlob(bytes.NewReader(content))

	if !reg.BlobExists(digest) {
		t.Fatal("blob should exist after write")
	}

	if err := reg.DeleteBlob(digest); err != nil {
		t.Fatalf("DeleteBlob: %v", err)
	}

	if reg.BlobExists(digest) {
		t.Error("blob should not exist after delete")
	}
}

func TestNewCreatesDirectories(t *testing.T) {
	dir := filepath.Join(t.TempDir(), "oci-storage")
	reg, err := oci.New(dir)
	if err != nil {
		t.Fatal(err)
	}

	for _, sub := range []string{"blobs/sha256", "uploads"} {
		p := filepath.Join(dir, sub)
		if _, err := os.Stat(p); os.IsNotExist(err) {
			t.Errorf("directory not created: %s", p)
		}
	}
	_ = reg
}
