package core

import (
	"context"
	"errors"
	"testing"

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

type MockStartable struct {
	started bool
	err     error
}

func (m *MockStartable) OnStartup(ctx context.Context) error {
	m.started = true
	return m.err
}

type MockStoppable struct {
	stopped bool
	err     error
}

func (m *MockStoppable) OnShutdown(ctx context.Context) error {
	m.stopped = true
	return m.err
}

type MockLifecycle struct {
	MockStartable
	MockStoppable
}

func TestCore_LifecycleInterfaces(t *testing.T) {
	c, err := New()
	assert.NoError(t, err)

	startable := &MockStartable{}
	stoppable := &MockStoppable{}
	lifecycle := &MockLifecycle{}

	// Register services
	err = c.RegisterService("startable", startable)
	assert.NoError(t, err)
	err = c.RegisterService("stoppable", stoppable)
	assert.NoError(t, err)
	err = c.RegisterService("lifecycle", lifecycle)
	assert.NoError(t, err)

	// Startup
	err = c.ServiceStartup(context.Background(), nil)
	assert.NoError(t, err)
	assert.True(t, startable.started)
	assert.True(t, lifecycle.started)
	assert.False(t, stoppable.stopped)

	// Shutdown
	err = c.ServiceShutdown(context.Background())
	assert.NoError(t, err)
	assert.True(t, stoppable.stopped)
	assert.True(t, lifecycle.stopped)
}

type MockLifecycleWithLog struct {
	id  string
	log *[]string
}

func (m *MockLifecycleWithLog) OnStartup(ctx context.Context) error {
	*m.log = append(*m.log, "start-"+m.id)
	return nil
}

func (m *MockLifecycleWithLog) OnShutdown(ctx context.Context) error {
	*m.log = append(*m.log, "stop-"+m.id)
	return nil
}

func TestCore_LifecycleOrder(t *testing.T) {
	c, err := New()
	assert.NoError(t, err)

	var callOrder []string

	s1 := &MockLifecycleWithLog{id: "1", log: &callOrder}
	s2 := &MockLifecycleWithLog{id: "2", log: &callOrder}

	err = c.RegisterService("s1", s1)
	assert.NoError(t, err)
	err = c.RegisterService("s2", s2)
	assert.NoError(t, err)

	// Startup
	err = c.ServiceStartup(context.Background(), nil)
	assert.NoError(t, err)
	assert.Equal(t, []string{"start-1", "start-2"}, callOrder)

	// Reset log
	callOrder = nil

	// Shutdown
	err = c.ServiceShutdown(context.Background())
	assert.NoError(t, err)
	assert.Equal(t, []string{"stop-2", "stop-1"}, callOrder)
}

func TestCore_LifecycleErrors(t *testing.T) {
	c, err := New()
	assert.NoError(t, err)

	s1 := &MockStartable{err: assert.AnError}
	s2 := &MockStoppable{err: assert.AnError}

	c.RegisterService("s1", s1)
	c.RegisterService("s2", s2)

	err = c.ServiceStartup(context.Background(), nil)
	assert.Error(t, err)
	assert.ErrorIs(t, err, assert.AnError)

	err = c.ServiceShutdown(context.Background())
	assert.Error(t, err)
	assert.ErrorIs(t, err, assert.AnError)
}

func TestCore_LifecycleErrors_Aggregated(t *testing.T) {
	c, err := New()
	assert.NoError(t, err)

	// Register action that fails
	c.RegisterAction(func(c *Core, msg Message) error {
		if _, ok := msg.(ActionServiceStartup); ok {
			return errors.New("startup action error")
		}
		if _, ok := msg.(ActionServiceShutdown); ok {
			return errors.New("shutdown action error")
		}
		return nil
	})

	// Register service that fails
	s1 := &MockStartable{err: errors.New("startup service error")}
	s2 := &MockStoppable{err: errors.New("shutdown service error")}

	err = c.RegisterService("s1", s1)
	assert.NoError(t, err)
	err = c.RegisterService("s2", s2)
	assert.NoError(t, err)

	// Startup
	err = c.ServiceStartup(context.Background(), nil)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "startup action error")
	assert.Contains(t, err.Error(), "startup service error")

	// Shutdown
	err = c.ServiceShutdown(context.Background())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "shutdown action error")
	assert.Contains(t, err.Error(), "shutdown service error")
}
