package bot

import (
	"context"
	"testing"
	"time"

	storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
	mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory"
	"github.com/tech/sendico/pkg/model"
)

type fakeService struct{}

type fakeUserBindingResolver struct {
	bindings map[string]*UserBinding
	err      error
}

func (f fakeUserBindingResolver) ResolveUserBinding(_ context.Context, telegramUserID string) (*UserBinding, error) {
	if f.err != nil {
		return nil, f.err
	}
	if f.bindings == nil {
		//nolint:nilnil // Test stub uses nil, nil to represent missing binding.
		return nil, nil
	}
	return f.bindings[telegramUserID], nil
}

func (fakeService) ExecutionDelay() time.Duration {
	return 30 * time.Second
}

func (fakeService) MaxPerOperationLimit() string {
	return "1000000"
}

func (fakeService) GetActiveRequestForAccount(context.Context, string) (*storagemodel.TreasuryRequest, error) {
	//nolint:nilnil // Test stub uses nil, nil to represent no active request.
	return nil, nil
}

func (fakeService) GetAccountProfile(_ context.Context, ledgerAccountID string) (*AccountProfile, error) {
	return &AccountProfile{
		AccountID:   ledgerAccountID,
		AccountCode: ledgerAccountID,
		Currency:    "USD",
	}, nil
}

func (fakeService) CreateRequest(context.Context, CreateRequestInput) (*storagemodel.TreasuryRequest, error) {
	//nolint:nilnil // Test stub uses nil, nil to represent no created request.
	return nil, nil
}

func (fakeService) ConfirmRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) {
	//nolint:nilnil // Test stub uses nil, nil to represent no confirmed request.
	return nil, nil
}

func (fakeService) CancelRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) {
	//nolint:nilnil // Test stub uses nil, nil to represent no cancelled request.
	return nil, nil
}

func TestRouterUnauthorizedInAllowedChatSendsAccessDenied(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
					AllowedChatIDs:  []string{"100"},
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "100",
			FromUserID: "999",
			Text:       "/fund",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != unauthorizedMessage {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterUnknownChatGetsDenied(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
					AllowedChatIDs:  []string{"100"},
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "999",
			FromUserID: "123",
			Text:       "/fund",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != unauthorizedChatMessage {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterEmptyAllowedChats_AllowsAnyChatForAuthorizedUser(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "999",
			FromUserID: "123",
			Text:       "/fund",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != amountPromptMessage(
		storagemodel.TreasuryOperationFund,
		&AccountProfile{AccountID: "acct-1", AccountCode: "acct-1", Currency: "USD"},
		"acct-1",
	) {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterEmptyAllowedChats_UnauthorizedUserGetsDenied(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "777",
			FromUserID: "999",
			Text:       "/fund",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != unauthorizedMessage {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterStartAuthorizedShowsWelcome(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "777",
			FromUserID: "123",
			Text:       "/start",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != welcomeMessage(&AccountProfile{AccountID: "acct-1", AccountCode: "acct-1", Currency: "USD"}) {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterHelpAuthorizedShowsHelp(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "777",
			FromUserID: "123",
			Text:       "/help",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != helpMessage("acct-1", "USD") {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterStartUnauthorizedGetsDenied(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "777",
			FromUserID: "999",
			Text:       "/start",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != unauthorizedMessage {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}

func TestRouterPlainTextWithoutSession_ShowsSupportedCommands(t *testing.T) {
	var sent []string
	router := NewRouter(
		mloggerfactory.NewLogger(false),
		fakeService{},
		func(_ context.Context, _ string, text string) error {
			sent = append(sent, text)
			return nil
		},
		nil,
		fakeUserBindingResolver{
			bindings: map[string]*UserBinding{
				"123": {
					TelegramUserID:  "123",
					LedgerAccountID: "acct-1",
				},
			},
		},
	)
	handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
		Message: &model.TelegramMessage{
			ChatID:     "777",
			FromUserID: "123",
			Text:       "hello",
		},
	})
	if !handled {
		t.Fatalf("expected update to be handled")
	}
	if len(sent) != 1 {
		t.Fatalf("expected one message, got %d", len(sent))
	}
	if sent[0] != supportedCommandsMessage() {
		t.Fatalf("unexpected message: %q", sent[0])
	}
}
