package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
	"time"

	"gitea.inhome.blapointe.com/local/storage"

	"github.com/mmcdole/gofeed"
)

type stringReaderCloser struct {
	io.Reader
}

func mockReadClose(s string) io.ReadCloser {
	reader := strings.NewReader(s)
	return stringReaderCloser{Reader: reader}
}

func (src stringReaderCloser) Close() error {
	return nil
}

func fakeRSSServer() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
         <?xml version="1.0" encoding="UTF-8" ?>
         <rss version="2.0">
             <channel>
                 <item>
                     <title>Item Title</title>
                     <link>https://roosterteeth.com/episode/rooster-teeth-podcast-2019-549</link>
                     <pubDate>Tue, 18 Jun 2019 19:00:00 +0000</pubDate>
                     <description>Gavin Free discuss raditation, toilet paper face, Chris's continued haircuts, and more on this week's RT Podcast! magnet:-xt1 magnet:-xt2 <a href="magnet:-xt3">link</a></description>
                     <enclosure url="http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/roosterteethpodcast/Rooster_Teeth_Podcast_549.mp3" type="audio/mpeg" />
                 </item>
            </channel>
         </rss>
      `))
	}))
}

func TestMainLoopCtx(t *testing.T) {
	ctx, can := context.WithCancel(context.Background())
	can()
	c := &Config{
		interval: time.Hour,
		ctx:      ctx,
	}
	if err := mainLoop(c); err == nil || !strings.Contains(err.Error(), "cancel") {
		t.Fatal(err)
	}
}

func TestConfig(t *testing.T) {
	was := os.Args[:]
	defer func() {
		os.Args = was
	}()
	os.Args = []string{"a"}
	if _, err := config(); err != nil {
		t.Fatal(err)
	}

	os.Args = []string{"a", "-interval", "not a duration"}
	stderr := os.Stderr
	f, _ := os.Open("/dev/null")
	os.Stderr = f
	defer func() {
		os.Stderr = stderr
	}()
	if _, err := config(); err == nil {
		t.Fatal(err)
	}
}

func TestGetGoFeed(t *testing.T) {
	s := fakeRSSServer()
	defer s.Close()

	f, err := getGoFeed(s.URL)
	if err != nil {
		t.Fatal(err)
	}
	if len(f.Items) != 1 {
		t.Fatal(len(f.Items))
	}
}

func TestGetItemContent(t *testing.T) {
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`Hello`))
	}))
	defer s.Close()

	cases := []struct {
		item gofeed.Item
		body string
		err  error
	}{
		{
			item: gofeed.Item{
				Description: "hi",
				Content:     "hi2",
			},
			body: "hi",
			err:  nil,
		},
		{
			item: gofeed.Item{
				Content: "hi2",
			},
			body: "hi2",
			err:  nil,
		},
		{
			item: gofeed.Item{
				Link: s.URL,
			},
			body: "Hello",
			err:  nil,
		},
	}

	for i, c := range cases {
		body, err := getItemContent(&c.item)
		cerrS := fmt.Sprintf("%v", c.err)
		errS := fmt.Sprintf("%v", err)
		if cerrS != errS {
			t.Errorf("[%d] unexpected err %v, want %v", i, err, c.err)
		}
		if body != c.body {
			t.Errorf("[%d] unexpected body %v, want %v", i, body, c.body)
		}
	}
}

func TestIsDone(t *testing.T) {
	db, _ := storage.New(storage.MAP)
	db.Set("a", []byte("hi"))
	if ok, err := isDone(db, "a"); err != nil {
		t.Fatal(err)
	} else if !ok {
		t.Fatal(ok)
	}

	if ok, err := isDone(db, "b"); err != nil {
		t.Fatal(err)
	} else if ok {
		t.Fatal(ok)
	}
}

func TestGetSessionID(t *testing.T) {
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add(sessionHeader, "id")
		w.Write([]byte(`Hello`))
	}))
	defer s.Close()

	session, err := getSessionID(s.URL)
	if err != nil {
		t.Fatal(err)
	}

	if session != "id" {
		t.Fatal(session)
	}
}

func TestBuildReqBody(t *testing.T) {
	var want struct {
		Method    string `json:"method"`
		Arguments struct {
			Filename    string `json:"filename"`
			DownloadDir string `json:"download-dir"`
		} `json:"arguments"`
	}

	b := buildReqBody("out", "mag")
	if err := json.NewDecoder(b).Decode(&want); err != nil {
		t.Fatal(err)
	}
	if want.Method != "torrent-add" {
		t.Fatal(want.Method)
	}
	if want.Arguments.Filename != "mag" {
		t.Fatal(want.Arguments.Filename)
	}
	if want.Arguments.DownloadDir != "out" {
		t.Fatal(want.Arguments.DownloadDir)
	}
}

func TestSucceeded(t *testing.T) {
	cases := []struct {
		s   string
		err error
	}{
		{
			s:   `{"result":"success"}`,
			err: nil,
		},
		{
			s:   `this isnt json`,
			err: errors.New("invalid character 'h' in literal true (expecting 'r')"),
		},
		{
			s:   `{"result":"failure"}`,
			err: errors.New(`denied: {"result":"failure"}`),
		},
	}

	for i, c := range cases {
		err := succeeded(mockReadClose(c.s))
		cerrS := fmt.Sprintf("%v", c.err)
		errS := fmt.Sprintf("%v", err)
		if cerrS != errS {
			t.Errorf("[%d] unexpected err %v, want %v", i, err, c.err)
		}
	}
}

func TestFindMagnets(t *testing.T) {
	cases := []struct {
		s string
		l int
	}{
		{
			s: `here is some magnet:-xt1 and magnet:-xt2 another one <a href="magnet:-xt3">link</a>`,
			l: 3,
		},
		{
			s: `here is a link https://my.domain/a.b/c.torrent, and another <a href="https://my.domain/a/b/c.torrent"></a>`,
			l: 2,
		},
	}

	for i, c := range cases {
		out := findMagnets(c.s)
		if len(out) != c.l {
			t.Errorf("[%d] found %v magnets, want %v", i, len(out), c.l)
		}
	}
}
