package table

import (
	"bytes"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
)

type tableObjectSample struct {
	ID   int
	Name string
}

type tableListSample struct {
	ID   int
	Name string
}

type tablePointerSample struct {
	ID   int
	Name string
}

type tableMissingSchemeSample struct {
	ID int
}

type tableWriterSample struct {
	ID int
}

type errWriter struct {
	err error
}

func (w errWriter) Write([]byte) (int, error) {
	return 0, w.err
}

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

	SchemeStore.Add(tableObjectSample{}, []string{"ID", "Name"}, func(obj any) []any {
		value := obj.(tableObjectSample)
		return []any{value.ID, value.Name}
	})

	var output bytes.Buffer
	err := NewTable(&output).PrintObject(tableObjectSample{ID: 1, Name: "workspace"})

	require.NoError(t, err)
	require.Equal(t, " ID  Name      \n 1   workspace \n", output.String())
}

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

	SchemeStore.Add(tableListSample{}, []string{"ID", "Name"}, func(obj any) []any {
		value := obj.(tableListSample)
		return []any{value.ID, value.Name}
	})

	var output bytes.Buffer
	err := NewTable(&output).PrintList([]any{
		tableListSample{ID: 1, Name: "workspace-a"},
		tableListSample{ID: 2, Name: "workspace-b"},
	})

	require.NoError(t, err)
	require.Equal(t, " ID  Name        \n 1   workspace-a \n 2   workspace-b \n", output.String())
}

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

	var output bytes.Buffer
	err := NewTable(&output).PrintList(nil)

	require.NoError(t, err)
	require.Empty(t, output.String())
}

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

	SchemeStore.Add(tablePointerSample{}, []string{"ID", "Name"}, func(obj any) []any {
		value := obj.(*tablePointerSample)
		return []any{value.ID, value.Name}
	})

	var output bytes.Buffer
	err := NewTable(&output).PrintObject(&tablePointerSample{ID: 1, Name: "workspace"})

	require.NoError(t, err)
	require.Equal(t, " ID  Name      \n 1   workspace \n", output.String())
}

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

	err := NewTable(&bytes.Buffer{}).PrintObject(tableMissingSchemeSample{ID: 1})

	require.Error(t, err)
	require.ErrorContains(t, err, "not found scheme for object")
	require.ErrorContains(t, err, "tableMissingSchemeSample")
}

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

	SchemeStore.Add(tableWriterSample{}, []string{"ID"}, func(obj any) []any {
		value := obj.(tableWriterSample)
		return []any{value.ID}
	})

	writeErr := errors.New("write failed")
	err := NewTable(errWriter{err: writeErr}).PrintObject(tableWriterSample{ID: 1})

	require.ErrorIs(t, err, writeErr)
}
