// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package data

import (
	"bytes"
	"html/template"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"

	"github.com/bep/logg"
	"github.com/gohugoio/hugo/common/hmaps"
	"github.com/gohugoio/hugo/htesting"

	qt "github.com/frankban/quicktest"
)

func TestGetCSV(t *testing.T) {
	t.Parallel()
	htesting.SkipSlowTestUnlessCI(t)

	c := qt.New(t)

	for i, test := range []struct {
		sep     string
		url     string
		content string
		expect  any
	}{
		// Remotes
		{
			",",
			`http://success/`,
			"gomeetup,city\nyes,Sydney\nyes,San Francisco\nyes,Stockholm\n",
			[][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}},
		},
		{
			",",
			`http://error.extra.field/`,
			"gomeetup,city\nyes,Sydney\nyes,San Francisco\nyes,Stockholm,EXTRA\n",
			false,
		},
		{
			",",
			`http://nofound/404`,
			``,
			false,
		},

		// Locals
		{
			";",
			"pass/semi",
			"gomeetup;city\nyes;Sydney\nyes;San Francisco\nyes;Stockholm\n",
			[][]string{{"gomeetup", "city"}, {"yes", "Sydney"}, {"yes", "San Francisco"}, {"yes", "Stockholm"}},
		},
		{
			";",
			"fail/no-file",
			"",
			false,
		},
	} {
		c.Run(test.url, func(c *qt.C) {
			msg := qt.Commentf("Test %d", i)

			ns := newTestNs()

			// Setup HTTP test server
			var srv *httptest.Server
			srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
				if !hasHeaderValue(r.Header, "Accept", "text/csv") && !hasHeaderValue(r.Header, "Accept", "text/plain") {
					http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
					return
				}

				if r.URL.Path == "/404" {
					http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
					return
				}

				w.Header().Add("Content-type", "text/csv")

				w.Write([]byte(test.content))
			})
			defer func() { srv.Close() }()

			// Setup local test file for schema-less URLs
			if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
				f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Conf.BaseConfig().WorkingDir, test.url))
				c.Assert(err, qt.IsNil, msg)
				f.WriteString(test.content)
				f.Close()
			}

			// Get on with it
			got, err := ns.GetCSV(test.sep, test.url)

			if _, ok := test.expect.(bool); ok {
				c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 1)
				c.Assert(got, qt.IsNil)
				return
			}

			c.Assert(err, qt.IsNil, msg)
			c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0)
			c.Assert(got, qt.Not(qt.IsNil), msg)
			c.Assert(got, qt.DeepEquals, test.expect, msg)
		})
	}
}

func TestGetJSON(t *testing.T) {
	t.Parallel()
	htesting.SkipSlowTestUnlessCI(t)
	c := qt.New(t)

	for i, test := range []struct {
		url     string
		content string
		expect  any
	}{
		{
			`http://success/`,
			`{"gomeetup":["Sydney","San Francisco","Stockholm"]}`,
			map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}},
		},
		{
			`http://malformed/`,
			`{gomeetup:["Sydney","San Francisco","Stockholm"]}`,
			false,
		},
		{
			`http://nofound/404`,
			``,
			false,
		},
		// Locals
		{
			"pass/semi",
			`{"gomeetup":["Sydney","San Francisco","Stockholm"]}`,
			map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}},
		},
		{
			"fail/no-file",
			"",
			false,
		},
		{
			`pass/üńīçøðê-url.json`,
			`{"gomeetup":["Sydney","San Francisco","Stockholm"]}`,
			map[string]any{"gomeetup": []any{"Sydney", "San Francisco", "Stockholm"}},
		},
	} {
		c.Run(test.url, func(c *qt.C) {
			msg := qt.Commentf("Test %d", i)
			ns := newTestNs()

			// Setup HTTP test server
			var srv *httptest.Server
			srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
				if !hasHeaderValue(r.Header, "Accept", "application/json") {
					http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
					return
				}

				if r.URL.Path == "/404" {
					http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
					return
				}

				w.Header().Add("Content-type", "application/json")

				w.Write([]byte(test.content))
			})
			defer func() { srv.Close() }()

			// Setup local test file for schema-less URLs
			if !strings.Contains(test.url, ":") && !strings.HasPrefix(test.url, "fail/") {
				f, err := ns.deps.Fs.Source.Create(filepath.Join(ns.deps.Conf.BaseConfig().WorkingDir, test.url))
				c.Assert(err, qt.IsNil, msg)
				f.WriteString(test.content)
				f.Close()
			}

			// Get on with it
			got, _ := ns.GetJSON(test.url)

			if _, ok := test.expect.(bool); ok {
				c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 1)
				return
			}

			c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0, msg)
			c.Assert(got, qt.Not(qt.IsNil), msg)
			c.Assert(got, qt.DeepEquals, test.expect)
		})
	}
}

func TestHeaders(t *testing.T) {
	t.Parallel()
	c := qt.New(t)

	for _, test := range []struct {
		name    string
		headers any
		assert  func(c *qt.C, headers string)
	}{
		{
			`Misc header variants`,
			map[string]any{
				"Accept-Charset": "utf-8",
				"Max-forwards":   "10",
				"X-Int":          32,
				"X-Templ":        template.HTML("a"),
				"X-Multiple":     []string{"a", "b"},
				"X-MultipleInt":  []int{3, 4},
			},
			func(c *qt.C, headers string) {
				c.Assert(headers, qt.Contains, "Accept-Charset: utf-8")
				c.Assert(headers, qt.Contains, "Max-Forwards: 10")
				c.Assert(headers, qt.Contains, "X-Int: 32")
				c.Assert(headers, qt.Contains, "X-Templ: a")
				c.Assert(headers, qt.Contains, "X-Multiple: a")
				c.Assert(headers, qt.Contains, "X-Multiple: b")
				c.Assert(headers, qt.Contains, "X-Multipleint: 3")
				c.Assert(headers, qt.Contains, "X-Multipleint: 4")
				c.Assert(headers, qt.Contains, "User-Agent: Hugo Static Site Generator")
			},
		},
		{
			`Params`,
			hmaps.Params{
				"Accept-Charset": "utf-8",
			},
			func(c *qt.C, headers string) {
				c.Assert(headers, qt.Contains, "Accept-Charset: utf-8")
			},
		},
		{
			`Override User-Agent`,
			map[string]any{
				"User-Agent": "007",
			},
			func(c *qt.C, headers string) {
				c.Assert(headers, qt.Contains, "User-Agent: 007")
			},
		},
	} {
		c.Run(test.name, func(c *qt.C) {
			ns := newTestNs()

			// Setup HTTP test server
			var srv *httptest.Server
			var headers bytes.Buffer
			srv, ns.client = getTestServer(func(w http.ResponseWriter, r *http.Request) {
				c.Assert(r.URL.String(), qt.Equals, "http://gohugo.io/api?foo")
				w.Write([]byte("{}"))
				r.Header.Write(&headers)
			})
			defer func() { srv.Close() }()

			testFunc := func(fn func(args ...any) error) {
				defer headers.Reset()
				err := fn("http://example.org/api", "?foo", test.headers)

				c.Assert(err, qt.IsNil)
				c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0)
				test.assert(c, headers.String())
			}

			testFunc(func(args ...any) error {
				_, err := ns.GetJSON(args...)
				return err
			})
			testFunc(func(args ...any) error {
				_, err := ns.GetCSV(",", args...)
				return err
			})
		})
	}
}

func TestToURLAndHeaders(t *testing.T) {
	t.Parallel()
	c := qt.New(t)
	url, headers := toURLAndHeaders([]any{"https://foo?id=", 32})
	c.Assert(url, qt.Equals, "https://foo?id=32")
	c.Assert(headers, qt.IsNil)

	url, headers = toURLAndHeaders([]any{"https://foo?id=", 32, map[string]any{"a": "b"}})
	c.Assert(url, qt.Equals, "https://foo?id=32")
	c.Assert(headers, qt.DeepEquals, map[string]any{"a": "b"})
}

func TestParseCSV(t *testing.T) {
	t.Parallel()
	c := qt.New(t)

	for i, test := range []struct {
		csv []byte
		sep string
		exp string
		err bool
	}{
		{[]byte("a,b,c\nd,e,f\n"), "", "", true},
		{[]byte("a,b,c\nd,e,f\n"), "~/", "", true},
		{[]byte("a,b,c\nd,e,f"), "|", "a,b,cd,e,f", false},
		{[]byte("q,w,e\nd,e,f"), ",", "qwedef", false},
		{[]byte("a|b|c\nd|e|f|g"), "|", "abcdefg", true},
		{[]byte("z|y|c\nd|e|f"), "|", "zycdef", false},
	} {
		msg := qt.Commentf("Test %d: %v", i, test)

		csv, err := parseCSV(test.csv, test.sep)
		if test.err {
			c.Assert(err, qt.Not(qt.IsNil), msg)
			continue
		}
		c.Assert(err, qt.IsNil, msg)

		act := ""
		for _, v := range csv {
			act = act + strings.Join(v, "")
		}

		c.Assert(act, qt.Equals, test.exp, msg)
	}
}
