package initdata

import (
	"encoding/hex"
	"errors"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"testing"
	"time"
)

const testToken = "123456:TESTTOKEN"

// signInitData builds a validly signed initData query string for the given token
// and decoded fields, mirroring Telegram's data-check algorithm.
func signInitData(token string, fields map[string]string) string {
	keys := make([]string, 0, len(fields))
	for k := range fields {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	lines := make([]string, 0, len(keys))
	for _, k := range keys {
		lines = append(lines, k+"="+fields[k])
	}
	secret := hmacSHA256([]byte("WebAppData"), []byte(token))
	mac := hmacSHA256(secret, []byte(strings.Join(lines, "\n")))

	v := url.Values{}
	for k, val := range fields {
		v.Set(k, val)
	}
	v.Set("hash", hex.EncodeToString(mac))
	return v.Encode()
}

func freshFields() map[string]string {
	return map[string]string{
		"auth_date": strconv.FormatInt(time.Now().Unix(), 10),
		"user":      `{"id":42,"username":"neo","first_name":"Thomas","language_code":"ru"}`,
	}
}

func TestValidateOK(t *testing.T) {
	initData := signInitData(testToken, freshFields())
	u, err := NewHMACValidator(testToken).Validate(initData)
	if err != nil {
		t.Fatalf("validate: %v", err)
	}
	if u.ExternalID != "42" || u.Username != "neo" || u.FirstName != "Thomas" || u.LanguageCode != "ru" {
		t.Errorf("user = %+v, want {42 neo Thomas ru}", u)
	}
}

func TestValidateRejects(t *testing.T) {
	valid := signInitData(testToken, freshFields())

	t.Run("tampered hash", func(t *testing.T) {
		tampered := strings.Replace(valid, "hash=", "hash=00", 1)
		if _, err := NewHMACValidator(testToken).Validate(tampered); !errors.Is(err, ErrInvalidInitData) {
			t.Errorf("err = %v, want ErrInvalidInitData", err)
		}
	})
	t.Run("wrong token", func(t *testing.T) {
		if _, err := NewHMACValidator("other:TOKEN").Validate(valid); !errors.Is(err, ErrInvalidInitData) {
			t.Errorf("err = %v, want ErrInvalidInitData", err)
		}
	})
	t.Run("missing hash", func(t *testing.T) {
		if _, err := NewHMACValidator(testToken).Validate("user=%7B%7D&auth_date=1"); !errors.Is(err, ErrInvalidInitData) {
			t.Errorf("err = %v, want ErrInvalidInitData", err)
		}
	})
	t.Run("stale auth_date", func(t *testing.T) {
		stale := signInitData(testToken, map[string]string{
			"auth_date": strconv.FormatInt(time.Now().Add(-48*time.Hour).Unix(), 10),
			"user":      `{"id":42}`,
		})
		if _, err := NewHMACValidator(testToken).Validate(stale); !errors.Is(err, ErrInvalidInitData) {
			t.Errorf("err = %v, want ErrInvalidInitData", err)
		}
	})
}

func TestValidateBotUser(t *testing.T) {
	t.Run("is_bot true is denied", func(t *testing.T) {
		initData := signInitData(testToken, map[string]string{
			"auth_date": strconv.FormatInt(time.Now().Unix(), 10),
			"user":      `{"id":42,"is_bot":true,"first_name":"Robo"}`,
		})
		if _, err := NewHMACValidator(testToken).Validate(initData); !errors.Is(err, ErrInvalidInitData) {
			t.Errorf("err = %v, want ErrInvalidInitData", err)
		}
	})
	t.Run("is_bot false is allowed", func(t *testing.T) {
		initData := signInitData(testToken, map[string]string{
			"auth_date": strconv.FormatInt(time.Now().Unix(), 10),
			"user":      `{"id":42,"is_bot":false,"first_name":"Thomas"}`,
		})
		u, err := NewHMACValidator(testToken).Validate(initData)
		if err != nil {
			t.Fatalf("validate: %v", err)
		}
		if u.ExternalID != "42" || u.FirstName != "Thomas" {
			t.Errorf("user = %+v, want {42 Thomas}", u)
		}
	})
}
