package bot

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/go-telegram/bot/models"
	"go.uber.org/zap"
)

// fakeBotAPI answers getMe (so bot.New succeeds offline) and records the last
// sendMessage form fields.
type fakeBotAPI struct {
	chatID      string
	text        string
	replyMarkup string
}

func (f *fakeBotAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch {
	case strings.HasSuffix(r.URL.Path, "/getMe"):
		io.WriteString(w, `{"ok":true,"result":{"id":1,"is_bot":true,"first_name":"test","username":"testbot"}}`)
	case strings.HasSuffix(r.URL.Path, "/sendMessage"):
		f.chatID = r.FormValue("chat_id")
		f.text = r.FormValue("text")
		f.replyMarkup = r.FormValue("reply_markup")
		io.WriteString(w, `{"ok":true,"result":{"message_id":1}}`)
	case strings.HasSuffix(r.URL.Path, "/getChat"):
		// Echo the requested id into the username so a resolver test can tell the
		// channel lookup from the chat lookup.
		io.WriteString(w, `{"ok":true,"result":{"id":-100,"type":"channel","username":"u`+r.FormValue("chat_id")+`"}}`)
	default:
		io.WriteString(w, `{"ok":true,"result":true}`)
	}
}

func newTestBot(t *testing.T, api http.Handler) *Bot {
	t.Helper()
	srv := httptest.NewServer(api)
	t.Cleanup(srv.Close)
	b, err := New(Config{Token: "123:ABC", APIBaseURL: srv.URL, MiniAppURL: "https://example.com/telegram/"}, zap.NewNop())
	if err != nil {
		t.Fatalf("new bot: %v", err)
	}
	return b
}

func TestNotifyBuildsLaunchButton(t *testing.T) {
	api := &fakeBotAPI{}
	b := newTestBot(t, api)
	if err := b.Notify(context.Background(), 12345, "It's your turn.", "Open game", "g7c9e"); err != nil {
		t.Fatalf("notify: %v", err)
	}
	if api.chatID != "12345" {
		t.Errorf("chat_id = %q, want 12345", api.chatID)
	}
	if api.text != "It's your turn." {
		t.Errorf("text = %q", api.text)
	}
	if !strings.Contains(api.replyMarkup, "web_app") || !strings.Contains(api.replyMarkup, "startapp=g7c9e") {
		t.Errorf("reply_markup = %q, want a web_app button with startapp=g7c9e", api.replyMarkup)
	}
}

func TestSendTextHasNoMarkup(t *testing.T) {
	api := &fakeBotAPI{}
	b := newTestBot(t, api)
	if err := b.SendText(context.Background(), 999, "plain"); err != nil {
		t.Fatalf("send text: %v", err)
	}
	if api.chatID != "999" || api.text != "plain" {
		t.Errorf("chat_id=%q text=%q, want 999/plain", api.chatID, api.text)
	}
	if api.replyMarkup != "" {
		t.Errorf("reply_markup = %q, want empty", api.replyMarkup)
	}
}

// getMePathFor captures the path bot.New's getMe call hits for the given TestEnv,
// so the test environment routing is covered (a misroute is exactly what makes a
// test-environment token fail with "getMe unauthorized").
func getMePathFor(t *testing.T, testEnv bool) string {
	t.Helper()
	var path string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.HasSuffix(r.URL.Path, "/getMe") {
			path = r.URL.Path
		}
		io.WriteString(w, `{"ok":true,"result":{"id":1,"is_bot":true,"first_name":"t","username":"tb"}}`)
	}))
	t.Cleanup(srv.Close)
	if _, err := New(Config{Token: "123:ABC", APIBaseURL: srv.URL, TestEnv: testEnv, MiniAppURL: "https://example.com/"}, zap.NewNop()); err != nil {
		t.Fatalf("new bot (testEnv=%v): %v", testEnv, err)
	}
	return path
}

func TestTestEnvironmentRoutesGetMe(t *testing.T) {
	if got, want := getMePathFor(t, true), "/bot123:ABC/test/getMe"; got != want {
		t.Errorf("TestEnv getMe path = %q, want %q", got, want)
	}
	if got, want := getMePathFor(t, false), "/bot123:ABC/getMe"; got != want {
		t.Errorf("prod getMe path = %q, want %q", got, want)
	}
}

func TestHandleStartRepliesPrivateOnly(t *testing.T) {
	t.Run("private replies in english by default", func(t *testing.T) {
		api := &fakeBotAPI{}
		b := newTestBot(t, api)
		b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{
			Chat: models.Chat{ID: 42, Type: models.ChatTypePrivate}, Text: "/start g7",
		}})
		if api.chatID != "42" || !strings.Contains(api.replyMarkup, "web_app") {
			t.Errorf("private /start: chat=%q markup=%q, want a web_app reply", api.chatID, api.replyMarkup)
		}
		// No reported language -> English welcome + English button.
		if !strings.Contains(api.text, "Hi!") {
			t.Errorf("text = %q, want the English welcome", api.text)
		}
		if !strings.Contains(api.replyMarkup, "Open") {
			t.Errorf("reply_markup = %q, want the English button", api.replyMarkup)
		}
	})
	t.Run("uses the sender's reported language", func(t *testing.T) {
		api := &fakeBotAPI{}
		b := newTestBot(t, api)
		b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{
			Chat: models.Chat{ID: 42, Type: models.ChatTypePrivate}, Text: "/start",
			From: &models.User{ID: 7, LanguageCode: "ru"},
		}})
		if !strings.Contains(api.text, "Привет!") {
			t.Errorf("text = %q, want the Russian welcome for a ru sender", api.text)
		}
	})
	t.Run("embeds resolved follow handles", func(t *testing.T) {
		api := &fakeBotAPI{}
		b := newTestBot(t, api)
		b.channelUsername, b.chatUsername = "erudit", "erudite_chat"
		b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{
			Chat: models.Chat{ID: 42, Type: models.ChatTypePrivate}, Text: "/start",
		}})
		if !strings.Contains(api.text, "@erudit") || !strings.Contains(api.text, "@erudite_chat") {
			t.Errorf("text = %q, want the follow handles", api.text)
		}
	})
	t.Run("group ignored", func(t *testing.T) {
		api := &fakeBotAPI{}
		b := newTestBot(t, api)
		b.handleStart(context.Background(), b.api, &models.Update{Message: &models.Message{
			Chat: models.Chat{ID: -100, Type: models.ChatTypeSupergroup}, Text: "/start",
		}})
		if api.chatID != "" {
			t.Errorf("group /start got a reply (chat=%q); an inline web_app button is invalid in groups", api.chatID)
		}
	})
}

func TestResolveWelcomeHandles(t *testing.T) {
	api := &fakeBotAPI{}
	b := newTestBot(t, api)
	b.channelID, b.chatID = 111, 222
	b.resolveWelcomeHandles(context.Background())
	// The fake echoes the requested id into the username, so each lookup is independent.
	if b.channelUsername != "u111" {
		t.Errorf("channelUsername = %q, want u111", b.channelUsername)
	}
	if b.chatUsername != "u222" {
		t.Errorf("chatUsername = %q, want u222", b.chatUsername)
	}
	// An unset id resolves to no handle (and makes no getChat call).
	b.channelID = 0
	b.resolveWelcomeHandles(context.Background())
	if b.channelUsername != "" {
		t.Errorf("channelUsername = %q, want empty for id 0", b.channelUsername)
	}
}

func TestStartPayload(t *testing.T) {
	cases := map[string]string{
		"/start g123":  "g123",
		"/start":       "",
		"/start  f99 ": "f99",
		"hello":        "",
	}
	for in, want := range cases {
		if got := startPayload(in); got != want {
			t.Errorf("startPayload(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestLaunchURL(t *testing.T) {
	b := &Bot{miniAppURL: "https://example.com/telegram/"}
	if got := b.launchURL(""); got != "https://example.com/telegram/" {
		t.Errorf("empty start param = %q, want the base URL", got)
	}
	if got := b.launchURL("g123"); !strings.Contains(got, "startapp=g123") {
		t.Errorf("launchURL = %q, want startapp=g123", got)
	}
}
