package handler

import (
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"
	"webp_server_go/config"
	"webp_server_go/helper"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/etag"
	"github.com/patrickmn/go-cache"
	"github.com/stretchr/testify/assert"
)

var (
	chromeUA   = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36"
	safariUA   = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15"
	safari17UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3.1 Safari/605.1.15" // <- Mac with Safari 17
	curlUA     = "curl/7.64.1"

	acceptWebP   = "image/webp,image/apng,image/*,*/*;q=0.8"
	acceptAvif   = "image/avif,image/*,*/*;q=0.8"
	acceptLegacy = "image/jpeg,image/png"
)

func setupParam() {
	// setup parameters here...
	config.Config.ImgPath = "../pics"
	config.Config.ExhaustPath = "../exhaust_test"
	config.Config.AllowedTypes = []string{"jpg", "png", "jpeg", "bmp"}
	config.Metadata = "../metadata"
	config.RemoteRaw = "../remote-raw"
	config.ProxyMode = false
	config.Config.EnableWebP = true
	config.Config.EnableAVIF = false
	config.Config.Quality = 80
	config.Config.ImageMap = map[string]string{}
	config.RemoteCache = cache.New(cache.NoExpiration, 10*time.Minute)
}

func requestToServer(reqUrl string, app *fiber.App, ua, accept string) (*http.Response, []byte) {
	parsedUrl, _ := url.Parse(reqUrl)
	req := httptest.NewRequest("GET", parsedUrl.EscapedPath(), nil)
	req.Header.Set("User-Agent", ua)
	req.Header.Set("Accept", accept)
	req.Header.Set("Host", parsedUrl.Host)
	req.Host = parsedUrl.Host
	resp, err := app.Test(req, 120000)
	if err != nil {
		return nil, nil
	}
	data, _ := io.ReadAll(resp.Body)
	return resp, data
}

func TestServerHeaders(t *testing.T) {
	setupParam()
	var app = fiber.New()
	app.Use(etag.New(etag.Config{
		Weak: true,
	}))
	app.Get("/*", Convert)
	url := "http://127.0.0.1:3333/webp_server.bmp"

	// test for chrome
	response, _ := requestToServer(url, app, chromeUA, acceptWebP)
	defer response.Body.Close()
	ratio := response.Header.Get("X-Compression-Rate")
	etag := response.Header.Get("Etag")

	assert.NotEqual(t, "", ratio)
	assert.NotEqual(t, "", etag)

	// test for safari
	response, _ = requestToServer(url, app, safariUA, acceptLegacy)
	defer response.Body.Close()
	// ratio = response.Header.Get("X-Compression-Rate")
	etag = response.Header.Get("Etag")

	assert.NotEqual(t, "", etag)
}

func TestConvertDuplicates(t *testing.T) {
	setupParam()
	N := 3

	var testLink = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg":                 "image/webp",
		"http://127.0.0.1:3333/webp_server.bmp":                 "image/webp",
		"http://127.0.0.1:3333/webp_server.png":                 "image/webp",
		"http://127.0.0.1:3333/empty.jpg":                       "",
		"http://127.0.0.1:3333/png.jpg":                         "image/webp",
		"http://127.0.0.1:3333/12314.jpg":                       "",
		"http://127.0.0.1:3333/dir1/inside.jpg":                 "image/webp",
		"http://127.0.0.1:3333/%e5%a4%aa%e7%a5%9e%e5%95%a6.png": "image/webp",
		"http://127.0.0.1:3333/太神啦.png":                         "image/webp",
	}

	var app = fiber.New()
	app.Get("/*", Convert)

	// test Chrome
	for url, respType := range testLink {
		for range N {
			resp, data := requestToServer(url, app, chromeUA, acceptWebP)
			defer resp.Body.Close()
			contentType := helper.GetContentType(data)
			assert.Equal(t, respType, contentType)
		}
	}

}
func TestConvert(t *testing.T) {
	setupParam()
	// TODO: old-style test, better update it with accept headers
	var testChromeLink = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg":                 "image/webp",
		"http://127.0.0.1:3333/webp_server.bmp":                 "image/webp",
		"http://127.0.0.1:3333/webp_server.png":                 "image/webp",
		"http://127.0.0.1:3333/empty.jpg":                       "",
		"http://127.0.0.1:3333/png.jpg":                         "image/webp",
		"http://127.0.0.1:3333/12314.jpg":                       "",
		"http://127.0.0.1:3333/dir1/inside.jpg":                 "image/webp",
		"http://127.0.0.1:3333/%e5%a4%aa%e7%a5%9e%e5%95%a6.png": "image/webp",
		"http://127.0.0.1:3333/太神啦.png":                         "image/webp",
	}

	var testChromeAvifLink = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg":                 "image/avif",
		"http://127.0.0.1:3333/webp_server.bmp":                 "image/avif",
		"http://127.0.0.1:3333/webp_server.png":                 "image/avif",
		"http://127.0.0.1:3333/empty.jpg":                       "",
		"http://127.0.0.1:3333/png.jpg":                         "image/avif",
		"http://127.0.0.1:3333/12314.jpg":                       "",
		"http://127.0.0.1:3333/dir1/inside.jpg":                 "image/avif",
		"http://127.0.0.1:3333/%e5%a4%aa%e7%a5%9e%e5%95%a6.png": "image/avif",
		"http://127.0.0.1:3333/太神啦.png":                         "image/avif",
	}

	var testSafariLink = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg": "image/jpeg",
		"http://127.0.0.1:3333/webp_server.bmp": "image/png", // png instead oft bmp because ResizeItself() uses ExportNative()
		"http://127.0.0.1:3333/webp_server.png": "image/png",
		"http://127.0.0.1:3333/empty.jpg":       "",
		"http://127.0.0.1:3333/png.jpg":         "image/png",
		"http://127.0.0.1:3333/12314.jpg":       "",
		"http://127.0.0.1:3333/dir1/inside.jpg": "image/jpeg",
	}

	var app = fiber.New()
	app.Get("/*", Convert)

	// // test Chrome
	for url, respType := range testChromeLink {
		resp, data := requestToServer(url, app, chromeUA, acceptWebP)
		defer resp.Body.Close()
		contentType := helper.GetContentType(data)
		assert.Equal(t, respType, contentType)
	}

	// test Safari
	for url, respType := range testSafariLink {
		resp, data := requestToServer(url, app, safariUA, acceptLegacy)
		defer resp.Body.Close()
		contentType := helper.GetContentType(data)
		assert.Equal(t, respType, contentType)
	}

	// test Avif is processed in proxy mode
	config.Config.EnableAVIF = true
	for url, respType := range testChromeAvifLink {
		resp, data := requestToServer(url, app, chromeUA, acceptAvif)
		defer resp.Body.Close()
		contentType := helper.GetContentType(data)
		assert.NotNil(t, respType)
		assert.Equal(t, respType, contentType)
	}
}

func TestConvertNotAllowed(t *testing.T) {
	setupParam()
	config.Config.AllowedTypes = []string{"jpg", "png", "jpeg"}

	var app = fiber.New()
	app.Get("/*", Convert)

	// not allowed, but we have the file, this should return File extension not allowed
	url := "http://127.0.0.1:3333/webp_server.bmp"
	resp, data := requestToServer(url, app, chromeUA, acceptWebP)
	defer resp.Body.Close()
	assert.Contains(t, string(data), "File extension not allowed")

	// not allowed, random file
	url = url + "hagdgd"
	resp, data = requestToServer(url, app, chromeUA, acceptWebP)
	defer resp.Body.Close()
	assert.Contains(t, string(data), "File extension not allowed")

}

func TestConvertProxyModeBad(t *testing.T) {
	setupParam()
	config.ProxyMode = true

	var app = fiber.New()
	app.Get("/*", Convert)

	// this is local random image, should be 404
	url := "http://127.0.0.1:3333/webp_8888server.bmp"
	resp, _ := requestToServer(url, app, chromeUA, acceptWebP)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusNotFound, resp.StatusCode)

	// this is local random image, test using cURL, should be 404, ref: https://github.com/webp-sh/webp_server_go/issues/197
	resp1, _ := requestToServer(url, app, curlUA, acceptWebP)
	defer resp1.Body.Close()
	assert.Equal(t, http.StatusNotFound, resp1.StatusCode)

}

func TestConvertProxyModeWork(t *testing.T) {
	setupParam()
	config.ProxyMode = true
	config.Config.ImgPath = "https://docs.webp.sh"

	var app = fiber.New()
	app.Get("/*", Convert)

	url := "http://127.0.0.1:3333/images/webp_server.jpg"

	resp, data := requestToServer(url, app, chromeUA, acceptWebP)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	assert.Equal(t, "image/webp", helper.GetContentType(data))

	// test proxyMode with Safari
	resp, data = requestToServer(url, app, safariUA, acceptLegacy)
	defer resp.Body.Close()
	assert.Equal(t, http.StatusOK, resp.StatusCode)
	assert.Equal(t, "image/jpeg", helper.GetContentType(data))
}

func TestConvertProxyImgMap(t *testing.T) {
	setupParam()
	config.ProxyMode = false
	config.Config.ImageMap = map[string]string{
		"/2":                            "../pics/dir1",
		"/3":                            "../pics3",             // Invalid path, does not exists
		"www.invalid-path.com":          "https://docs.webp.sh", // Invalid, it does not start with '/'
		"/www.weird-path.com":           "https://docs.webp.sh",
		"/www.even-more-werid-path.com": "https://docs.webp.sh/images",
		"http://example.com":            "https://docs.webp.sh",
	}

	var app = fiber.New()
	app.Get("/*", Convert)

	var testUrls = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg":                              "image/webp",
		"http://127.0.0.1:3333/2/inside.jpg":                                 "image/webp",
		"http://127.0.0.1:3333/www.weird-path.com/images/webp_server.jpg":    "image/webp",
		"http://127.0.0.1:3333/www.even-more-werid-path.com/webp_server.jpg": "image/webp",
		"http://example.com//images/webp_server.jpg":                         "image/webp",
	}

	var testUrlsLegacy = map[string]string{
		"http://127.0.0.1:3333/webp_server.jpg":     "image/jpeg",
		"http://127.0.0.1:3333/2/inside.jpg":        "image/jpeg",
		"http://example.com/images/webp_server.jpg": "image/jpeg",
	}

	var testUrlsInvalid = map[string]string{
		"http://127.0.0.1:3333/3/does-not-exist.jpg":         "", // Dir mapped does not exist
		"http://127.0.0.1:3333/www.weird-path.com/cover.jpg": "", // Host mapped, final URI invalid
	}

	for url, respType := range testUrls {
		resp, data := requestToServer(url, app, chromeUA, acceptWebP)
		defer resp.Body.Close()
		assert.Equal(t, http.StatusOK, resp.StatusCode)
		assert.Equal(t, respType, helper.GetContentType(data))
	}

	// tests with Safari
	for url, respType := range testUrlsLegacy {
		resp, data := requestToServer(url, app, safariUA, acceptLegacy)
		defer resp.Body.Close()
		assert.Equal(t, http.StatusOK, resp.StatusCode)
		assert.Equal(t, respType, helper.GetContentType(data))
	}

	for url, respType := range testUrlsInvalid {
		resp, data := requestToServer(url, app, safariUA, acceptLegacy)
		defer resp.Body.Close()
		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
		assert.Equal(t, respType, helper.GetContentType(data))
	}
}

func TestConvertProxyImgMapCWD(t *testing.T) {
	setupParam()
	config.ProxyMode = false
	config.Config.ImgPath = ".." // equivalent to "" when not testing
	config.Config.ImageMap = map[string]string{
		"/1":                     "../pics/dir1",
		"/2":                     "../pics",
		"/3":                     "../pics", // Invalid path, does not exists
		"http://www.example.com": "https://docs.webp.sh",
	}

	var app = fiber.New()
	app.Get("/*", Convert)

	var testUrls = map[string]string{
		"http://127.0.0.1:3333/1/inside.jpg":            "image/webp",
		"http://127.0.0.1:3333/2/webp_server.jpg":       "image/webp",
		"http://127.0.0.1:3333/3/webp_server.jpg":       "image/webp",
		"http://www.example.com/images/webp_server.jpg": "image/webp",
	}

	for url, respType := range testUrls {
		resp, data := requestToServer(url, app, chromeUA, acceptWebP)
		defer resp.Body.Close()
		assert.Equal(t, http.StatusOK, resp.StatusCode)
		assert.Equal(t, respType, helper.GetContentType(data))
	}
}

func TestConvertBigger(t *testing.T) {
	setupParam()
	config.Config.Quality = 100

	var app = fiber.New()
	app.Get("/*", Convert)

	url := "http://127.0.0.1:3333/big.jpg"
	resp, data := requestToServer(url, app, chromeUA, acceptWebP)
	defer resp.Body.Close()
	assert.Equal(t, "image/jpeg", resp.Header.Get("content-type"))
	assert.Equal(t, "image/jpeg", helper.GetContentType(data))
	_ = os.RemoveAll(config.Config.ExhaustPath)
}
