package channel

import (
	"context"
	"errors"
	"testing"
)

type fakeLifecycleStore struct {
	resolveFunc func(ctx context.Context, botID string, channelType ChannelType) (ChannelConfig, error)
	upsertFunc  func(ctx context.Context, botID string, channelType ChannelType, req UpsertConfigRequest) (ChannelConfig, error)
	statusFunc  func(ctx context.Context, botID string, channelType ChannelType, disabled bool) (ChannelConfig, error)
	deleteFunc  func(ctx context.Context, botID string, channelType ChannelType) error
}

func (f *fakeLifecycleStore) ResolveEffectiveConfig(ctx context.Context, botID string, channelType ChannelType) (ChannelConfig, error) {
	if f.resolveFunc == nil {
		return ChannelConfig{}, ErrChannelConfigNotFound
	}
	return f.resolveFunc(ctx, botID, channelType)
}

func (f *fakeLifecycleStore) UpsertConfig(ctx context.Context, botID string, channelType ChannelType, req UpsertConfigRequest) (ChannelConfig, error) {
	if f.upsertFunc == nil {
		return ChannelConfig{}, nil
	}
	return f.upsertFunc(ctx, botID, channelType, req)
}

func (f *fakeLifecycleStore) UpdateConfigDisabled(ctx context.Context, botID string, channelType ChannelType, disabled bool) (ChannelConfig, error) {
	if f.statusFunc == nil {
		return ChannelConfig{}, ErrChannelConfigNotFound
	}
	return f.statusFunc(ctx, botID, channelType, disabled)
}

func (f *fakeLifecycleStore) DeleteConfig(ctx context.Context, botID string, channelType ChannelType) error {
	if f.deleteFunc == nil {
		return nil
	}
	return f.deleteFunc(ctx, botID, channelType)
}

type fakeConnectionController struct {
	ensureFunc func(ctx context.Context, cfg ChannelConfig) error
	removeFunc func(ctx context.Context, botID string, channelType ChannelType)
}

func (f *fakeConnectionController) EnsureConnection(ctx context.Context, cfg ChannelConfig) error {
	if f.ensureFunc == nil {
		return nil
	}
	return f.ensureFunc(ctx, cfg)
}

func (f *fakeConnectionController) RemoveConnection(ctx context.Context, botID string, channelType ChannelType) {
	if f.removeFunc == nil {
		return
	}
	f.removeFunc(ctx, botID, channelType)
}

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

	removeCalled := false
	store := &fakeLifecycleStore{
		upsertFunc: func(ctx context.Context, botID string, channelType ChannelType, req UpsertConfigRequest) (ChannelConfig, error) {
			return ChannelConfig{ID: "cfg-1", BotID: botID, ChannelType: channelType, Disabled: true}, nil
		},
	}
	controller := &fakeConnectionController{
		removeFunc: func(ctx context.Context, botID string, channelType ChannelType) {
			removeCalled = true
		},
	}
	service := NewLifecycle(store, controller)
	disabled := true

	cfg, err := service.UpsertBotChannelConfig(context.Background(), "bot-1", ChannelType("telegram"), UpsertConfigRequest{
		Credentials: map[string]any{"botToken": "x"},
		Disabled:    &disabled,
	})
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !cfg.Disabled {
		t.Fatalf("expected disabled config")
	}
	if !removeCalled {
		t.Fatalf("expected remove connection to be called")
	}
}

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

	previous := ChannelConfig{
		ID:          "cfg-prev",
		BotID:       "bot-1",
		ChannelType: ChannelType("telegram"),
		Credentials: map[string]any{"botToken": "old"},
		Disabled:    false,
	}
	newConfig := ChannelConfig{
		ID:          "cfg-new",
		BotID:       "bot-1",
		ChannelType: ChannelType("telegram"),
		Credentials: map[string]any{"botToken": "new"},
		Disabled:    false,
	}
	upsertCalls := 0
	ensureCalls := 0
	store := &fakeLifecycleStore{
		resolveFunc: func(ctx context.Context, botID string, channelType ChannelType) (ChannelConfig, error) {
			return previous, nil
		},
		upsertFunc: func(ctx context.Context, botID string, channelType ChannelType, req UpsertConfigRequest) (ChannelConfig, error) {
			upsertCalls++
			if upsertCalls == 1 {
				return newConfig, nil
			}
			if token := ReadString(req.Credentials, "botToken"); token != "old" {
				t.Fatalf("expected rollback credentials old, got %s", token)
			}
			return previous, nil
		},
	}
	controller := &fakeConnectionController{
		ensureFunc: func(ctx context.Context, cfg ChannelConfig) error {
			ensureCalls++
			if ensureCalls == 1 {
				return errors.New("dial failed")
			}
			return nil
		},
	}
	service := NewLifecycle(store, controller)
	enabled := false

	_, err := service.UpsertBotChannelConfig(context.Background(), "bot-1", ChannelType("telegram"), UpsertConfigRequest{
		Credentials: map[string]any{"botToken": "new"},
		Disabled:    &enabled,
	})
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
	if upsertCalls != 2 {
		t.Fatalf("expected 2 upsert calls (write + rollback), got %d", upsertCalls)
	}
	if ensureCalls != 2 {
		t.Fatalf("expected 2 ensure calls (new + restore), got %d", ensureCalls)
	}
}

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

	deleteCalls := 0
	store := &fakeLifecycleStore{
		resolveFunc: func(ctx context.Context, botID string, channelType ChannelType) (ChannelConfig, error) {
			return ChannelConfig{}, ErrChannelConfigNotFound
		},
		upsertFunc: func(ctx context.Context, botID string, channelType ChannelType, req UpsertConfigRequest) (ChannelConfig, error) {
			return ChannelConfig{
				ID:          "cfg-new",
				BotID:       botID,
				ChannelType: channelType,
				Credentials: map[string]any{"botToken": "new"},
			}, nil
		},
		deleteFunc: func(ctx context.Context, botID string, channelType ChannelType) error {
			deleteCalls++
			return nil
		},
	}
	controller := &fakeConnectionController{
		ensureFunc: func(ctx context.Context, cfg ChannelConfig) error {
			return errors.New("start failed")
		},
	}
	service := NewLifecycle(store, controller)
	enabled := false

	_, err := service.UpsertBotChannelConfig(context.Background(), "bot-1", ChannelType("telegram"), UpsertConfigRequest{
		Credentials: map[string]any{"botToken": "new"},
		Disabled:    &enabled,
	})
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
	if deleteCalls != 1 {
		t.Fatalf("expected 1 delete call for rollback, got %d", deleteCalls)
	}
}

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

	removeCalled := false
	store := &fakeLifecycleStore{
		deleteFunc: func(ctx context.Context, botID string, channelType ChannelType) error {
			return nil
		},
	}
	controller := &fakeConnectionController{
		removeFunc: func(ctx context.Context, botID string, channelType ChannelType) {
			removeCalled = true
		},
	}
	service := NewLifecycle(store, controller)

	if err := service.DeleteBotChannelConfig(context.Background(), "bot-1", ChannelType("telegram")); err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !removeCalled {
		t.Fatalf("expected remove connection to be called")
	}
}

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

	removeCalled := false
	store := &fakeLifecycleStore{
		statusFunc: func(ctx context.Context, botID string, channelType ChannelType, disabled bool) (ChannelConfig, error) {
			if !disabled {
				t.Fatalf("expected disabled=true update")
			}
			return ChannelConfig{ID: "cfg-1", BotID: botID, ChannelType: channelType, Disabled: true}, nil
		},
	}
	controller := &fakeConnectionController{
		removeFunc: func(ctx context.Context, botID string, channelType ChannelType) {
			removeCalled = true
		},
	}
	service := NewLifecycle(store, controller)

	cfg, err := service.SetBotChannelStatus(context.Background(), "bot-1", ChannelType("telegram"), true)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !cfg.Disabled {
		t.Fatalf("expected disabled config")
	}
	if !removeCalled {
		t.Fatalf("expected remove connection to be called")
	}
}

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

	statusCalls := 0
	removeCalled := false
	store := &fakeLifecycleStore{
		statusFunc: func(ctx context.Context, botID string, channelType ChannelType, disabled bool) (ChannelConfig, error) {
			statusCalls++
			if statusCalls == 1 && disabled {
				t.Fatalf("first status update should enable config")
			}
			if statusCalls == 2 && !disabled {
				t.Fatalf("second status update should rollback to disabled=true")
			}
			return ChannelConfig{
				ID:          "cfg-1",
				BotID:       botID,
				ChannelType: channelType,
				Disabled:    disabled,
			}, nil
		},
	}
	controller := &fakeConnectionController{
		ensureFunc: func(ctx context.Context, cfg ChannelConfig) error {
			return errors.New("start failed")
		},
		removeFunc: func(ctx context.Context, botID string, channelType ChannelType) {
			removeCalled = true
		},
	}
	service := NewLifecycle(store, controller)

	_, err := service.SetBotChannelStatus(context.Background(), "bot-1", ChannelType("telegram"), false)
	if err == nil {
		t.Fatalf("expected error, got nil")
	}
	if statusCalls != 2 {
		t.Fatalf("expected 2 status updates, got %d", statusCalls)
	}
	if !removeCalled {
		t.Fatalf("expected remove connection to be called on failed enable")
	}
}
