package migration

import (
	"bytes"
	"errors"
	"io"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/tidbcloud/observability/vm-migration/internal/schema"
)

type bufferWriteCloser struct {
	bytes.Buffer
	closeErr error
	closed   int
}

func (w *bufferWriteCloser) Close() error {
	w.closed++
	return w.closeErr
}

type failingWriteCloser struct {
	writeErr error
	closeErr error
}

func (w *failingWriteCloser) Write([]byte) (int, error) { return 0, w.writeErr }
func (w *failingWriteCloser) Close() error              { return w.closeErr }

func TestTenantActionLogPersistsAndObserves(t *testing.T) {
	dir := t.TempDir()
	observer := &bufferWriteCloser{}
	var warnings []string
	log := openTenantActionLog(dir, schema.MigrationLoopTenant{
		Name:    "tenant-1",
		LogFile: "tenant-1.log",
	}, func(string) io.WriteCloser { return observer }, func(message string) {
		warnings = append(warnings, message)
	})

	payload := []byte("action output\n")
	n, err := log.Write(payload)
	assert.NoError(t, err)
	assert.Equal(t, len(payload), n)
	log.Close()

	durable, err := os.ReadFile(filepath.Join(dir, "tenant-1.log"))
	assert.NoError(t, err)
	assert.Equal(t, payload, durable)
	assert.Equal(t, payload, observer.Bytes())
	assert.Equal(t, 1, observer.closed)
	assert.Empty(t, warnings)
}

func TestTenantActionLogTargetFailureIsBestEffort(t *testing.T) {
	cases := []struct {
		name             string
		makeLog          func(*bufferWriteCloser, func(string)) *tenantActionLog
		wantHealthyBytes bool
	}{
		{
			name: "durable failure leaves observer healthy",
			makeLog: func(healthy *bufferWriteCloser, warn func(string)) *tenantActionLog {
				return &tenantActionLog{
					tenant:   "tenant-1",
					path:     "/unavailable/tenant-1.log",
					warn:     warn,
					durable:  &failingWriteCloser{writeErr: errors.New("disk failed")},
					observer: healthy,
				}
			},
			wantHealthyBytes: true,
		},
		{
			name: "observer failure leaves durable healthy",
			makeLog: func(healthy *bufferWriteCloser, warn func(string)) *tenantActionLog {
				return &tenantActionLog{
					tenant:   "tenant-1",
					path:     "tenant-1.log",
					warn:     warn,
					durable:  healthy,
					observer: &failingWriteCloser{writeErr: errors.New("stream failed")},
				}
			},
			wantHealthyBytes: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			healthy := &bufferWriteCloser{}
			var warnings []string
			log := tc.makeLog(healthy, func(message string) { warnings = append(warnings, message) })

			for range 2 {
				n, err := log.Write([]byte("line\n"))
				assert.NoError(t, err)
				assert.Equal(t, len("line\n"), n)
			}
			log.Close()

			if tc.wantHealthyBytes {
				assert.Equal(t, "line\nline\n", healthy.String())
			}
			assert.Len(t, warnings, 1, "a failing target should warn once")
		})
	}
}
