package local

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

	"git.marketally.com/tutus-one/tutus-chain/pkg/storage"
)

func TestNew(t *testing.T) {
	tmpDir := t.TempDir()

	adapter, err := New(Config{Path: tmpDir})
	if err != nil {
		t.Fatalf("New() error = %v", err)
	}
	defer adapter.Close()

	// Check subdirectories were created
	for _, sub := range []string{"objects", "meta", "blocks", "states"} {
		path := filepath.Join(tmpDir, sub)
		if _, err := os.Stat(path); os.IsNotExist(err) {
			t.Errorf("subdirectory %s not created", sub)
		}
	}
}

func TestNew_DefaultPath(t *testing.T) {
	// Clean up default path after test
	defer os.RemoveAll("./storage")

	adapter, err := New(Config{})
	if err != nil {
		t.Fatalf("New() error = %v", err)
	}
	defer adapter.Close()

	if _, err := os.Stat("./storage"); os.IsNotExist(err) {
		t.Error("default storage directory not created")
	}
}

func TestAdapter_Name(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	if got := adapter.Name(); got != "local" {
		t.Errorf("Name() = %v, want %v", got, "local")
	}
}

func TestAdapter_PutGet(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	data := []byte("test data")

	// Put
	id, err := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{
		ContentType: "text/plain",
		Attributes:  map[string]string{"key": "value"},
	})
	if err != nil {
		t.Fatalf("Put() error = %v", err)
	}

	if id.Provider != "local" {
		t.Errorf("Put() Provider = %v, want %v", id.Provider, "local")
	}

	// Get
	reader, err := adapter.Get(ctx, id)
	if err != nil {
		t.Fatalf("Get() error = %v", err)
	}
	defer reader.Close()

	got, err := io.ReadAll(reader)
	if err != nil {
		t.Fatalf("ReadAll() error = %v", err)
	}

	if !bytes.Equal(got, data) {
		t.Errorf("Get() = %v, want %v", got, data)
	}
}

func TestAdapter_Get_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	_, err := adapter.Get(ctx, storage.ObjectID{ID: "nonexistent"})

	if err != storage.ErrNotFound {
		t.Errorf("Get() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_Exists(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	data := []byte("test data")

	// Put
	id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{})

	// Exists should return true
	exists, err := adapter.Exists(ctx, id)
	if err != nil {
		t.Fatalf("Exists() error = %v", err)
	}
	if !exists {
		t.Error("Exists() = false, want true")
	}

	// Non-existent should return false
	exists, err = adapter.Exists(ctx, storage.ObjectID{ID: "nonexistent"})
	if err != nil {
		t.Fatalf("Exists() error = %v", err)
	}
	if exists {
		t.Error("Exists() = true, want false")
	}
}

func TestAdapter_Delete(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	data := []byte("test data")

	// Put
	id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{})

	// Delete
	err := adapter.Delete(ctx, id)
	if err != nil {
		t.Fatalf("Delete() error = %v", err)
	}

	// Should not exist anymore
	exists, _ := adapter.Exists(ctx, id)
	if exists {
		t.Error("object still exists after Delete()")
	}
}

func TestAdapter_Delete_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	err := adapter.Delete(ctx, storage.ObjectID{ID: "nonexistent"})

	if err != storage.ErrNotFound {
		t.Errorf("Delete() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_Head(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	data := []byte("test data")

	// Put
	id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{
		ContentType: "text/plain",
		Attributes:  map[string]string{"key": "value"},
	})

	// Head
	info, err := adapter.Head(ctx, id)
	if err != nil {
		t.Fatalf("Head() error = %v", err)
	}

	if info.Size != int64(len(data)) {
		t.Errorf("Head() Size = %v, want %v", info.Size, len(data))
	}
	if info.ContentType != "text/plain" {
		t.Errorf("Head() ContentType = %v, want %v", info.ContentType, "text/plain")
	}
	if info.Attributes["key"] != "value" {
		t.Errorf("Head() Attributes[key] = %v, want %v", info.Attributes["key"], "value")
	}
}

func TestAdapter_Head_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	_, err := adapter.Head(ctx, storage.ObjectID{ID: "nonexistent"})

	if err != storage.ErrNotFound {
		t.Errorf("Head() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_List(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// Put some objects
	ids := make([]storage.ObjectID, 3)
	for i := 0; i < 3; i++ {
		data := []byte(strings.Repeat("x", i+1)) // Different content = different ID
		id, _ := adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{})
		ids[i] = id
	}

	// List all
	results, err := adapter.List(ctx, "", storage.ListOptions{})
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}

	if len(results) != 3 {
		t.Errorf("List() returned %d items, want 3", len(results))
	}
}

func TestAdapter_List_MaxResults(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// Put some objects
	for i := 0; i < 5; i++ {
		data := []byte(strings.Repeat("x", i+1))
		adapter.Put(ctx, bytes.NewReader(data), storage.PutOptions{})
	}

	// List with limit
	results, err := adapter.List(ctx, "", storage.ListOptions{MaxResults: 2})
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}

	if len(results) != 2 {
		t.Errorf("List() returned %d items, want 2", len(results))
	}
}

func TestAdapter_QuotaExceeded(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100})
	defer adapter.Close()

	ctx := context.Background()

	// First put should succeed
	data1 := []byte(strings.Repeat("x", 50))
	_, err := adapter.Put(ctx, bytes.NewReader(data1), storage.PutOptions{})
	if err != nil {
		t.Fatalf("Put() error = %v", err)
	}

	// Second put should exceed quota
	data2 := []byte(strings.Repeat("y", 60))
	_, err = adapter.Put(ctx, bytes.NewReader(data2), storage.PutOptions{})
	if err != storage.ErrQuotaExceeded {
		t.Errorf("Put() error = %v, want %v", err, storage.ErrQuotaExceeded)
	}
}

// =============================================================================
// BlockStorage Tests
// =============================================================================

func TestAdapter_PutGetBlock(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	blockData := []byte("block 0 data")

	// PutBlock
	id, err := adapter.PutBlock(ctx, 0, blockData)
	if err != nil {
		t.Fatalf("PutBlock() error = %v", err)
	}

	if id.Container != "blocks" {
		t.Errorf("PutBlock() Container = %v, want %v", id.Container, "blocks")
	}

	// GetBlock
	got, err := adapter.GetBlock(ctx, 0)
	if err != nil {
		t.Fatalf("GetBlock() error = %v", err)
	}

	if !bytes.Equal(got, blockData) {
		t.Errorf("GetBlock() = %v, want %v", got, blockData)
	}
}

func TestAdapter_GetBlock_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	_, err := adapter.GetBlock(ctx, 999)

	if err != storage.ErrNotFound {
		t.Errorf("GetBlock() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_GetBlockRange(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// Put several blocks
	for i := uint32(0); i < 5; i++ {
		data := []byte(strings.Repeat(string(rune('a'+i)), 10))
		_, err := adapter.PutBlock(ctx, i, data)
		if err != nil {
			t.Fatalf("PutBlock(%d) error = %v", i, err)
		}
	}

	// Get range
	blocks, err := adapter.GetBlockRange(ctx, 1, 3)
	if err != nil {
		t.Fatalf("GetBlockRange() error = %v", err)
	}

	if len(blocks) != 3 {
		t.Errorf("GetBlockRange() returned %d blocks, want 3", len(blocks))
	}

	// Verify content
	for i, block := range blocks {
		expected := []byte(strings.Repeat(string(rune('b'+i)), 10))
		if !bytes.Equal(block, expected) {
			t.Errorf("GetBlockRange() block %d = %v, want %v", i+1, block, expected)
		}
	}
}

func TestAdapter_GetBlockRange_InvalidRange(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	_, err := adapter.GetBlockRange(ctx, 10, 5)

	if err == nil || !strings.Contains(err.Error(), "invalid block range") {
		t.Errorf("GetBlockRange() error = %v, want invalid block range error", err)
	}
}

func TestAdapter_GetBlockRange_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// Put only block 0
	adapter.PutBlock(ctx, 0, []byte("block 0"))

	// Try to get range that includes missing block
	_, err := adapter.GetBlockRange(ctx, 0, 2)

	if err != storage.ErrNotFound {
		t.Errorf("GetBlockRange() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_GetLatestBlockIndex(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// No blocks yet
	_, err := adapter.GetLatestBlockIndex(ctx)
	if err != storage.ErrNotFound {
		t.Errorf("GetLatestBlockIndex() error = %v, want %v", err, storage.ErrNotFound)
	}

	// Put some blocks
	for i := uint32(0); i < 10; i++ {
		adapter.PutBlock(ctx, i, []byte("block"))
	}

	// Get latest
	latest, err := adapter.GetLatestBlockIndex(ctx)
	if err != nil {
		t.Fatalf("GetLatestBlockIndex() error = %v", err)
	}

	if latest != 9 {
		t.Errorf("GetLatestBlockIndex() = %v, want 9", latest)
	}
}

func TestAdapter_PutBlock_QuotaExceeded(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100})
	defer adapter.Close()

	ctx := context.Background()

	// First block should succeed
	_, err := adapter.PutBlock(ctx, 0, []byte(strings.Repeat("x", 50)))
	if err != nil {
		t.Fatalf("PutBlock() error = %v", err)
	}

	// Second block should exceed quota
	_, err = adapter.PutBlock(ctx, 1, []byte(strings.Repeat("y", 60)))
	if err != storage.ErrQuotaExceeded {
		t.Errorf("PutBlock() error = %v, want %v", err, storage.ErrQuotaExceeded)
	}
}

// =============================================================================
// StateStorage Tests
// =============================================================================

func TestAdapter_PutGetState(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	stateData := []byte("state at height 0")

	// PutState
	id, err := adapter.PutState(ctx, 0, bytes.NewReader(stateData))
	if err != nil {
		t.Fatalf("PutState() error = %v", err)
	}

	if id.Container != "states" {
		t.Errorf("PutState() Container = %v, want %v", id.Container, "states")
	}

	// GetState
	reader, err := adapter.GetState(ctx, 0)
	if err != nil {
		t.Fatalf("GetState() error = %v", err)
	}
	defer reader.Close()

	got, err := io.ReadAll(reader)
	if err != nil {
		t.Fatalf("ReadAll() error = %v", err)
	}

	if !bytes.Equal(got, stateData) {
		t.Errorf("GetState() = %v, want %v", got, stateData)
	}
}

func TestAdapter_GetState_NotFound(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()
	_, err := adapter.GetState(ctx, 999)

	if err != storage.ErrNotFound {
		t.Errorf("GetState() error = %v, want %v", err, storage.ErrNotFound)
	}
}

func TestAdapter_GetLatestState(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir()})
	defer adapter.Close()

	ctx := context.Background()

	// No states yet
	_, _, err := adapter.GetLatestState(ctx)
	if err != storage.ErrNotFound {
		t.Errorf("GetLatestState() error = %v, want %v", err, storage.ErrNotFound)
	}

	// Put some states
	for i := uint32(0); i < 5; i++ {
		data := []byte(strings.Repeat(string(rune('a'+i)), 10))
		_, err := adapter.PutState(ctx, i, bytes.NewReader(data))
		if err != nil {
			t.Fatalf("PutState(%d) error = %v", i, err)
		}
	}

	// Get latest
	height, reader, err := adapter.GetLatestState(ctx)
	if err != nil {
		t.Fatalf("GetLatestState() error = %v", err)
	}
	defer reader.Close()

	if height != 4 {
		t.Errorf("GetLatestState() height = %v, want 4", height)
	}

	got, _ := io.ReadAll(reader)
	expected := []byte(strings.Repeat("e", 10))
	if !bytes.Equal(got, expected) {
		t.Errorf("GetLatestState() data = %v, want %v", got, expected)
	}
}

func TestAdapter_PutState_QuotaExceeded(t *testing.T) {
	adapter, _ := New(Config{Path: t.TempDir(), MaxSize: 100})
	defer adapter.Close()

	ctx := context.Background()

	// First state should succeed
	_, err := adapter.PutState(ctx, 0, bytes.NewReader([]byte(strings.Repeat("x", 50))))
	if err != nil {
		t.Fatalf("PutState() error = %v", err)
	}

	// Second state should exceed quota
	_, err = adapter.PutState(ctx, 1, bytes.NewReader([]byte(strings.Repeat("y", 60))))
	if err != storage.ErrQuotaExceeded {
		t.Errorf("PutState() error = %v, want %v", err, storage.ErrQuotaExceeded)
	}
}

// =============================================================================
// Path Helper Tests
// =============================================================================

func TestAdapter_blockPath(t *testing.T) {
	adapter := &Adapter{basePath: "/storage"}

	tests := []struct {
		index    uint32
		expected string
	}{
		{0, filepath.Join("/storage", "blocks", "0000", "0.block")},
		{9999, filepath.Join("/storage", "blocks", "0000", "9999.block")},
		{10000, filepath.Join("/storage", "blocks", "0001", "10000.block")},
		{12345, filepath.Join("/storage", "blocks", "0001", "12345.block")},
		{99999, filepath.Join("/storage", "blocks", "0009", "99999.block")},
	}

	for _, tt := range tests {
		got := adapter.blockPath(tt.index)
		if got != tt.expected {
			t.Errorf("blockPath(%d) = %v, want %v", tt.index, got, tt.expected)
		}
	}
}

func TestAdapter_statePath(t *testing.T) {
	adapter := &Adapter{basePath: "/storage"}

	tests := []struct {
		height   uint32
		expected string
	}{
		{0, filepath.Join("/storage", "states", "0000", "0.state")},
		{9999, filepath.Join("/storage", "states", "0000", "9999.state")},
		{10000, filepath.Join("/storage", "states", "0001", "10000.state")},
		{12345, filepath.Join("/storage", "states", "0001", "12345.state")},
	}

	for _, tt := range tests {
		got := adapter.statePath(tt.height)
		if got != tt.expected {
			t.Errorf("statePath(%d) = %v, want %v", tt.height, got, tt.expected)
		}
	}
}

// =============================================================================
// Interface Compliance Tests
// =============================================================================

func TestAdapter_ImplementsProvider(t *testing.T) {
	var _ storage.Provider = (*Adapter)(nil)
}

func TestAdapter_ImplementsBlockStorage(t *testing.T) {
	var _ storage.BlockStorage = (*Adapter)(nil)
}

func TestAdapter_ImplementsStateStorage(t *testing.T) {
	var _ storage.StateStorage = (*Adapter)(nil)
}
