package main

import (
	"encoding/json"
	"fmt"
	"github.com/stretchr/testify/assert"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// Define some custom types to make response parsing easier
type SearchResponse struct {
	SpotifyID  string
	Name       string
	Popularity int
	Genres     []string
}
type SearchResponseList []SearchResponse

func TestAlive(t *testing.T) {
	db := setupTestDatabase("testalive")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/alive", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, "yes!", w.Body.String())
}

func TestGetArtistByID(t *testing.T) {
	db := setupTestDatabase("testgetartistbyid")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists/0TnOYISbd1XYRBk9myaseg", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)

	// Dynamic data is hard to test so here we validate JSON response
	// And we check known fields which are not likely to change
	// We then check that the DB was updated correctly just to be sure
	var resp ArtistProfile
	err := json.NewDecoder(w.Body).Decode(&resp)
	if err != nil {
		assert.Fail(t, fmt.Sprintf("Could not validate and parse JSON response into ArtistProfile struct: %s", err.Error()))
	}
	assert.Equal(t, "0TnOYISbd1XYRBk9myaseg", resp.SpotifyID)
	assert.Equal(t, "Pitbull", resp.Name)

	var artist ArtistProfile
	dbResult := env.db.Take(&artist)
	if dbResult.Error != nil {
		assert.Fail(t, "Failed to retrieve new info from test database")
	}

	assert.Equal(t, "Pitbull", artist.Name)
	assert.Equal(t, "0TnOYISbd1XYRBk9myaseg", artist.SpotifyID)
}

func TestGetArtistByIDBadParams(t *testing.T) {
	db := setupTestDatabase("testgetartistbyidbadparams")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists/ExampleIDthatiscertainlyinvalid", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 400, w.Code)
	assert.Equal(t, `{"Error":"Bad request"}`, w.Body.String())
}

func TestGetArtistByName(t *testing.T) {
	db := setupTestDatabase("testgetartistbyname")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	// We should request Aesop's data so it is populated in the DB for this test
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists/2fSaE6BXtQy0x7R7v9IOmZ", nil)
	router.ServeHTTP(w, req)

	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/artists?name=aesop", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)

	var resp SearchResponseList
	err := json.NewDecoder(w.Body).Decode(&resp)
	if err != nil {
		assert.Fail(t, fmt.Sprintf("Could not validate and parse JSON response: %s", err.Error()))
	}

	// only response should be aesop
	assert.Equal(t, "Aesop Rock", resp[0].Name)
	assert.Equal(t, "2fSaE6BXtQy0x7R7v9IOmZ", resp[0].SpotifyID)
}

func TestGetArtistByNameEmptyParams(t *testing.T) {
	db := setupTestDatabase("testgetartistbynameemptyparams")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists?name=", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `[]`, w.Body.String())
}

func TestGetArtistByNameMissingParams(t *testing.T) {
	db := setupTestDatabase("testgetartistbynamemissingparams")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 400, w.Code)
	assert.Equal(t, `{"Error":"name parameter was not supplied"}`, w.Body.String())
}

func TestGetGenres(t *testing.T) {
	db := setupTestDatabase("testgetgenres")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/genres", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `[]`, w.Body.String())

	// Populate the DB with some genres and test again
	// This is aesop rock's ID which should return 3 genres
	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/artists/2fSaE6BXtQy0x7R7v9IOmZ", nil)
	router.ServeHTTP(w, req)

	w = httptest.NewRecorder()
	req, _ = http.NewRequest("GET", "/genres", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `["underground hip hop","experimental hip hop","alternative hip hop"]`, w.Body.String())
}

func TestCreateGenres(t *testing.T) {
	db := setupTestDatabase("testcreategenres")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/genres", strings.NewReader(`{"Name": "testgenre"}`))
	req.Header.Set("content-type", "application/json")
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `Success`, w.Body.String())

	// Confirm new genre was saved in DB
	var genre Genre
	dbResult := env.db.Find(&genre)
	if dbResult.Error != nil {
		assert.Fail(t, "Could not retrieve genre data from test DB")
	}

	assert.Equal(t, "testgenre", genre.Name)
}

func TestGetPaginatedGenres(t *testing.T) {
	db := setupTestDatabase("testgetpaginatedgenres")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	// Create a large list of fake genres to test paging
	var testData []Genre
	for i := 0; i < 100; i++ {
		testData = append(testData, Genre{Name: fmt.Sprintf("test%d", i)})
	}

	dbResult := env.db.Create(&testData)
	if dbResult.Error != nil {
		assert.Fail(t, "Failed to save test into info into database")
	}

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/genres?page=3&page_size=12", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	assert.Equal(t, `["test24","test25","test26","test27","test28","test29","test30","test31","test32","test33","test34","test35"]`, w.Body.String())
}

func TestGetPaginatedArtistByName(t *testing.T) {
	db := setupTestDatabase("testgetpaginatedartistbyname")
	env := &Env{db: db}
	router := setupRouter(env, spotifyClientID, spotifyClientSecret)

	// Create a large list of fake artist profiles to test paging
	var testData []ArtistProfile
	for i := 0; i < 100; i++ {
		testName := fmt.Sprintf("test%d", i)
		testArtist := ArtistProfile{
			Name:       testName,
			SpotifyID:  testName,
			Popularity: i,
			Genres:     []Genre{Genre{Name: testName}},
		}
		testData = append(testData, testArtist)
	}

	dbResult := env.db.Create(&testData)
	if dbResult.Error != nil {
		assert.Fail(t, "Failed to save test info into database")
	}

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/artists?name=test&page=15&page_size=2", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)
	var resp SearchResponseList
	err := json.NewDecoder(w.Body).Decode(&resp)
	if err != nil {
		assert.Fail(t, fmt.Sprintf("Could not validate and parse JSON response: %s", err.Error()))
	}

	assert.Equal(t, "test28", resp[0].SpotifyID)
	assert.Equal(t, "test28", resp[0].Name)
	assert.Equal(t, "test28", resp[0].Genres[0])
	assert.Equal(t, 28, resp[0].Popularity)

	// Test end of the returned data is correct too
	assert.Equal(t, "test29", resp[1].SpotifyID)
	assert.Equal(t, "test29", resp[1].Name)
	assert.Equal(t, "test29", resp[1].Genres[0])
	assert.Equal(t, 29, resp[1].Popularity)
}