package totp

import (
	"testing"
	"time"
)

// rfc6238Secret is the Base32 encoding of the ASCII seed "12345678901234567890"
// from RFC 6238 Appendix B (the SHA-1 test vector).
const rfc6238Secret = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"

func TestCode_RFC6238Vectors(t *testing.T) {
	// 8-digit reference values from RFC 6238 truncated to our 6 digits.
	cases := []struct {
		unix int64
		want string
	}{
		{59, "287082"},
		{1111111109, "081804"},
		{1111111111, "050471"},
		{1234567890, "005924"},
		{2000000000, "279037"},
		{20000000000, "353130"},
	}
	for _, c := range cases {
		got, err := Code(rfc6238Secret, time.Unix(c.unix, 0).UTC())
		if err != nil {
			t.Fatalf("Code(%d): %v", c.unix, err)
		}
		if got != c.want {
			t.Errorf("Code(%d) = %s; want %s", c.unix, got, c.want)
		}
	}
}

func TestValidate_SkewWindow(t *testing.T) {
	secret, err := GenerateSecret()
	if err != nil {
		t.Fatal(err)
	}
	now := time.Now().UTC()
	code, err := Code(secret, now)
	if err != nil {
		t.Fatal(err)
	}

	if !Validate(secret, code, now, 1) {
		t.Error("current code rejected")
	}
	// Previous window must be accepted with skew=1.
	if !Validate(secret, code, now.Add(Period), 1) {
		t.Error("code from previous step rejected with skew=1")
	}
	// Two steps away must be rejected.
	if Validate(secret, code, now.Add(2*Period+time.Second), 1) {
		t.Error("stale code accepted outside skew window")
	}
	// Wrong code rejected.
	if Validate(secret, "000000", now, 1) && code != "000000" {
		t.Error("validate accepted obviously wrong code")
	}
}

func TestValidate_BadInput(t *testing.T) {
	secret, _ := GenerateSecret()
	now := time.Now().UTC()
	if Validate(secret, "12345", now, 1) { // too short
		t.Error("accepted 5-digit code")
	}
	if Validate("not-base32!!", "123456", now, 1) {
		t.Error("accepted invalid secret")
	}
}

func TestProvisioningURI(t *testing.T) {
	uri := ProvisioningURI(rfc6238Secret, "admin", "Pangolin")
	if uri == "" {
		t.Fatal("empty URI")
	}
	for _, sub := range []string{"otpauth://totp/", "secret=" + rfc6238Secret, "issuer=Pangolin"} {
		if !contains(uri, sub) {
			t.Errorf("URI %q missing %q", uri, sub)
		}
	}
}

func contains(s, sub string) bool {
	for i := 0; i+len(sub) <= len(s); i++ {
		if s[i:i+len(sub)] == sub {
			return true
		}
	}
	return false
}
