// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package base

import (
	"encoding/base64"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestEncodeMD5(t *testing.T) {
	assert.Equal(t,
		"3858f62230ac3c915f300c664312c63f",
		EncodeMD5("foobar"),
	)
}

func TestEncodeSha1(t *testing.T) {
	assert.Equal(t,
		"8843d7f92416211de9ebb963ff4ce28125932878",
		EncodeSha1("foobar"),
	)
}

func TestEncodeSha256(t *testing.T) {
	assert.Equal(t,
		"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2",
		EncodeSha256("foobar"),
	)
}

func TestShortSha(t *testing.T) {
	assert.Equal(t, "veryverylo", ShortSha("veryverylong"))
}

func TestBasicAuthDecode(t *testing.T) {
	_, _, err := BasicAuthDecode("?")
	assert.Equal(t, "illegal base64 data at input byte 0", err.Error())

	user, pass, err := BasicAuthDecode("Zm9vOmJhcg==")
	assert.NoError(t, err)
	assert.Equal(t, "foo", user)
	assert.Equal(t, "bar", pass)

	_, _, err = BasicAuthDecode("aW52YWxpZA==")
	assert.Error(t, err)

	_, _, err = BasicAuthDecode("invalid")
	assert.Error(t, err)
}

func TestBasicAuthEncode(t *testing.T) {
	assert.Equal(t, "Zm9vOmJhcg==", BasicAuthEncode("foo", "bar"))
	assert.Equal(t, "MjM6IjotLS0t", BasicAuthEncode("23:\"", "----"))
}

func TestVerifyTimeLimitCode(t *testing.T) {
	tc := []struct {
		data    string
		minutes int
		code    string
		valid   bool
	}{{
		data:    "data",
		minutes: 2,
		code:    testCreateTimeLimitCode(t, "data", 2),
		valid:   true,
	}, {
		data:    "abc123-ß",
		minutes: 1,
		code:    testCreateTimeLimitCode(t, "abc123-ß", 1),
		valid:   true,
	}, {
		data:    "data",
		minutes: 2,
		code:    "2021012723240000005928251dac409d2c33a6eb82c63410aaad569bed",
		valid:   false,
	}}
	for _, test := range tc {
		actualValid := VerifyTimeLimitCode(test.data, test.minutes, test.code)
		assert.Equal(t, test.valid, actualValid, "data: '%s' code: '%s' should be valid: %t", test.data, test.code, test.valid)
	}
}

func testCreateTimeLimitCode(t *testing.T, data string, m int) string {
	result0 := CreateTimeLimitCode(data, m, nil)
	result1 := CreateTimeLimitCode(data, m, time.Now().Format("200601021504"))
	result2 := CreateTimeLimitCode(data, m, time.Unix(time.Now().Unix()+int64(time.Minute)*int64(m), 0).Format("200601021504"))

	assert.Equal(t, result0, result1)
	assert.NotEqual(t, result0, result2)

	assert.True(t, len(result0) != 0)
	return result0
}

func TestFileSize(t *testing.T) {
	var size int64 = 512
	assert.Equal(t, "512 B", FileSize(size))
	size *= 1024
	assert.Equal(t, "512 KiB", FileSize(size))
	size *= 1024
	assert.Equal(t, "512 MiB", FileSize(size))
	size *= 1024
	assert.Equal(t, "512 GiB", FileSize(size))
	size *= 1024
	assert.Equal(t, "512 TiB", FileSize(size))
	size *= 1024
	assert.Equal(t, "512 PiB", FileSize(size))
	size *= 4
	assert.Equal(t, "2.0 EiB", FileSize(size))
}

func TestPrettyNumber(t *testing.T) {
	assert.Equal(t, "23,342,432", PrettyNumber(23342432))
	assert.Equal(t, "0", PrettyNumber(0))
	assert.Equal(t, "-100,000", PrettyNumber(-100000))
}

func TestSubtract(t *testing.T) {
	toFloat64 := func(n interface{}) float64 {
		switch v := n.(type) {
		case int:
			return float64(v)
		case int8:
			return float64(v)
		case int16:
			return float64(v)
		case int32:
			return float64(v)
		case int64:
			return float64(v)
		case float32:
			return float64(v)
		case float64:
			return v
		default:
			return 0.0
		}
	}
	values := []interface{}{
		int(-3),
		int8(14),
		int16(81),
		int32(-156),
		int64(1528),
		float32(3.5),
		float64(-15.348),
	}
	for _, left := range values {
		for _, right := range values {
			expected := toFloat64(left) - toFloat64(right)
			sub := Subtract(left, right)
			assert.InDelta(t, expected, sub, 1e-3)
		}
	}
}

func TestEllipsisString(t *testing.T) {
	assert.Equal(t, "...", EllipsisString("foobar", 0))
	assert.Equal(t, "...", EllipsisString("foobar", 1))
	assert.Equal(t, "...", EllipsisString("foobar", 2))
	assert.Equal(t, "...", EllipsisString("foobar", 3))
	assert.Equal(t, "f...", EllipsisString("foobar", 4))
	assert.Equal(t, "fo...", EllipsisString("foobar", 5))
	assert.Equal(t, "foobar", EllipsisString("foobar", 6))
	assert.Equal(t, "foobar", EllipsisString("foobar", 10))
	assert.Equal(t, "测...", EllipsisString("测试文本一二三四", 4))
	assert.Equal(t, "测试...", EllipsisString("测试文本一二三四", 5))
	assert.Equal(t, "测试文...", EllipsisString("测试文本一二三四", 6))
	assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10))
}

func TestTruncateString(t *testing.T) {
	assert.Equal(t, "", TruncateString("foobar", 0))
	assert.Equal(t, "f", TruncateString("foobar", 1))
	assert.Equal(t, "fo", TruncateString("foobar", 2))
	assert.Equal(t, "foo", TruncateString("foobar", 3))
	assert.Equal(t, "foob", TruncateString("foobar", 4))
	assert.Equal(t, "fooba", TruncateString("foobar", 5))
	assert.Equal(t, "foobar", TruncateString("foobar", 6))
	assert.Equal(t, "foobar", TruncateString("foobar", 7))
	assert.Equal(t, "测试文本", TruncateString("测试文本一二三四", 4))
	assert.Equal(t, "测试文本一", TruncateString("测试文本一二三四", 5))
	assert.Equal(t, "测试文本一二", TruncateString("测试文本一二三四", 6))
	assert.Equal(t, "测试文本一二三", TruncateString("测试文本一二三四", 7))
}

func TestStringsToInt64s(t *testing.T) {
	testSuccess := func(input []string, expected []int64) {
		result, err := StringsToInt64s(input)
		assert.NoError(t, err)
		assert.Equal(t, expected, result)
	}
	testSuccess([]string{}, []int64{})
	testSuccess([]string{"-1234"}, []int64{-1234})
	testSuccess([]string{"1", "4", "16", "64", "256"},
		[]int64{1, 4, 16, 64, 256})

	_, err := StringsToInt64s([]string{"-1", "a", "$"})
	assert.Error(t, err)
}

func TestInt64sToStrings(t *testing.T) {
	assert.Equal(t, []string{}, Int64sToStrings([]int64{}))
	assert.Equal(t,
		[]string{"1", "4", "16", "64", "256"},
		Int64sToStrings([]int64{1, 4, 16, 64, 256}),
	)
}

func TestInt64sToMap(t *testing.T) {
	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
	assert.Equal(t,
		map[int64]bool{1: true, 4: true, 16: true},
		Int64sToMap([]int64{1, 4, 16}),
	)
}

func TestInt64sContains(t *testing.T) {
	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
	assert.Equal(t, true, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 1))
	assert.Equal(t, true, Int64sContains([]int64{2323}, 2323))
	assert.Equal(t, false, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232))
}

func TestIsLetter(t *testing.T) {
	assert.True(t, IsLetter('a'))
	assert.True(t, IsLetter('e'))
	assert.True(t, IsLetter('q'))
	assert.True(t, IsLetter('z'))
	assert.True(t, IsLetter('A'))
	assert.True(t, IsLetter('E'))
	assert.True(t, IsLetter('Q'))
	assert.True(t, IsLetter('Z'))
	assert.True(t, IsLetter('_'))
	assert.False(t, IsLetter('-'))
	assert.False(t, IsLetter('1'))
	assert.False(t, IsLetter('$'))
	assert.False(t, IsLetter(0x00))
	assert.False(t, IsLetter(0x93))
}

func TestDetectContentTypeLongerThanSniffLen(t *testing.T) {
	// Pre-condition: Shorter than sniffLen detects SVG.
	assert.Equal(t, "image/svg+xml", DetectContentType([]byte(`<!-- Comment --><svg></svg>`)))
	// Longer than sniffLen detects something else.
	assert.Equal(t, "text/plain; charset=utf-8", DetectContentType([]byte(`<!--
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment Comment Comment Comment Comment Comment Comment Comment
Comment Comment Comment --><svg></svg>`)))
}

// IsRepresentableAsText

func TestIsTextFile(t *testing.T) {
	assert.True(t, IsTextFile([]byte{}))
	assert.True(t, IsTextFile([]byte("lorem ipsum")))
}

func TestIsImageFile(t *testing.T) {
	png, _ := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAG0lEQVQYlWN4+vTpf3SMDTAMBYXYBLFpHgoKAeiOf0SGE9kbAAAAAElFTkSuQmCC")
	assert.True(t, IsImageFile(png))
	assert.False(t, IsImageFile([]byte("plain text")))
}

func TestIsSVGImageFile(t *testing.T) {
	assert.True(t, IsSVGImageFile([]byte("<svg></svg>")))
	assert.True(t, IsSVGImageFile([]byte("    <svg></svg>")))
	assert.True(t, IsSVGImageFile([]byte(`<svg width="100"></svg>`)))
	assert.True(t, IsSVGImageFile([]byte("<svg/>")))
	assert.True(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?><svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<!-- Comment -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<!-- Multiple -->
	<!-- Comments -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<!-- Multiline
	Comment -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN"
	"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?>
	<!-- Comment -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?>
	<!-- Multiple -->
	<!-- Comments -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?>
	<!-- Multline
	Comment -->
	<svg></svg>`)))
	assert.True(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
	<!-- Multline
	Comment -->
	<svg></svg>`)))
	assert.False(t, IsSVGImageFile([]byte{}))
	assert.False(t, IsSVGImageFile([]byte("svg")))
	assert.False(t, IsSVGImageFile([]byte("<svgfoo></svgfoo>")))
	assert.False(t, IsSVGImageFile([]byte("text<svg></svg>")))
	assert.False(t, IsSVGImageFile([]byte("<html><body><svg></svg></body></html>")))
	assert.False(t, IsSVGImageFile([]byte(`<script>"<svg></svg>"</script>`)))
	assert.False(t, IsSVGImageFile([]byte(`<!-- <svg></svg> inside comment -->
	<foo></foo>`)))
	assert.False(t, IsSVGImageFile([]byte(`<?xml version="1.0" encoding="UTF-8"?>
	<!-- <svg></svg> inside comment -->
	<foo></foo>`)))
}

func TestIsPDFFile(t *testing.T) {
	pdf, _ := base64.StdEncoding.DecodeString("JVBERi0xLjYKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQp4nF3NPwsCMQwF8D2f4s2CNYk1baF0EHRwOwg4iJt/NsFb/PpevUE4Mjwe")
	assert.True(t, IsPDFFile(pdf))
	assert.False(t, IsPDFFile([]byte("plain text")))
}

func TestIsVideoFile(t *testing.T) {
	mp4, _ := base64.StdEncoding.DecodeString("AAAAGGZ0eXBtcDQyAAAAAGlzb21tcDQyAAEI721vb3YAAABsbXZoZAAAAADaBlwX2gZcFwAAA+gA")
	assert.True(t, IsVideoFile(mp4))
	assert.False(t, IsVideoFile([]byte("plain text")))
}

func TestIsAudioFile(t *testing.T) {
	mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl")
	assert.True(t, IsAudioFile(mp3))
	assert.False(t, IsAudioFile([]byte("plain text")))
}

// TODO: Test EntryIcon

func TestSetupGiteaRoot(t *testing.T) {
	_ = os.Setenv("GITEA_ROOT", "test")
	assert.EqualValues(t, "test", SetupGiteaRoot())
	_ = os.Setenv("GITEA_ROOT", "")
	assert.NotEqual(t, "test", SetupGiteaRoot())
}

func TestFormatNumberSI(t *testing.T) {
	assert.Equal(t, "125", FormatNumberSI(int(125)))
	assert.Equal(t, "1.3k", FormatNumberSI(int64(1317)))
	assert.Equal(t, "21.3M", FormatNumberSI(21317675))
	assert.Equal(t, "45.7G", FormatNumberSI(45721317675))
	assert.Equal(t, "", FormatNumberSI("test"))
}