package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"testing"
)

type StubPlayerStore struct {
	scores   map[string]int
	winCalls []string
	league   []Player
}

func (s *StubPlayerStore) GetPlayerScore(name string) int {
	score := s.scores[name]
	return score
}

func (s *StubPlayerStore) RecordWin(name string) {
	s.winCalls = append(s.winCalls, name)
}

func (s *StubPlayerStore) GetLeague() League {
	return s.league
}

func TestGETPlayers(t *testing. T) {
	store := StubPlayerStore{
		map[string]int{
			"Pepper": 20,
			"Floyd":  10,
		},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("returns Pepper's score", func(t *testing. T) {
		request := newGetScoreRequest("Pepper")
		response := httptest. NewRecorder()

		server. ServeHTTP(response, request)

		assertStatus(t, response. Code, http. StatusOK)
		assertResponseBody(t, response. Body. String(), "20")
	})

	t.Run("returns Floyd's score", func(t *testing. T) {
		request := newGetScoreRequest("Floyd")
		response := httptest. NewRecorder()

		server. ServeHTTP(response, request)

		assertStatus(t, response. Code, http. StatusOK)
		assertResponseBody(t, response. Body. String(), "10")
	})

	t.Run("returns 404 on missing players", func(t *testing. T) {
		request := newGetScoreRequest("Apollo")
		response := httptest. NewRecorder()

		server. ServeHTTP(response, request)

		assertStatus(t, response. Code, http. StatusNotFound)
	})
}

func TestStoreWins(t *testing. T) {
	store := StubPlayerStore{
		map[string]int{},
		nil,
		nil,
	}
	server := NewPlayerServer(&store)

	t.Run("it records wins on POST", func(t *testing. T) {
		player := "Pepper"

		request := newPostWinRequest(player)
		response := httptest. NewRecorder()

		server. ServeHTTP(response, request)

		assertStatus(t, response. Code, http. StatusAccepted)

		if len(store.winCalls) ! = 1 {
			t.Fatalf("got %d calls to RecordWin want %d", len(store.winCalls), 1)
		}

		if store.winCalls[0] ! = player {
			t.Errorf("did not store correct winner got %q want %q", store.winCalls[0], player)
		}
	})
}

func TestLeague(t *testing. T) {

	t.Run("it returns the league table as JSON", func(t *testing. T) {
		wantedLeague := []Player{
			{"Cleo", 32},
			{"Chris", 20},
			{"Tiest", 14},
		}

		store := StubPlayerStore{nil, nil, wantedLeague}
		server := NewPlayerServer(&store)

		request := newLeagueRequest()
		response := httptest. NewRecorder()

		server. ServeHTTP(response, request)

		got := getLeagueFromResponse(t, response. Body)

		assertStatus(t, response. Code, http. StatusOK)
		assertLeague(t, got, wantedLeague)
		assertContentType(t, response, jsonContentType)

	})
}

func assertContentType(t *testing. T, response *httptest. ResponseRecorder, want string) {
	t.Helper()
	if response. Header().Get("content-type") ! = want {
		t.Errorf("response did not have content-type of %s, got %v", want, response. HeaderMap)
	}
}

func getLeagueFromResponse(t *testing. T, body io. Reader) []Player {
	t.Helper()
	league, err := NewLeague(body)

	if err ! = nil {
		t.Fatalf("Unable to parse response from server %q into slice of Player, '%v'", body, err)
	}

	return league
}

func assertLeague(t *testing. T, got, want []Player) {
	t.Helper()
	if ! reflect. DeepEqual(got, want) {
		t.Errorf("got %v want %v", got, want)
	}
}

func assertStatus(t *testing. T, got, want int) {
	t.Helper()
	if got ! = want {
		t.Errorf("did not get correct status, got %d, want %d", got, want)
	}
}

func newLeagueRequest() *http. Request {
	req, _ := http. NewRequest(http. MethodGet, "/league", nil)
	return req
}

func newGetScoreRequest(name string) *http. Request {
	req, _ := http. NewRequest(http. MethodGet, fmt. Sprintf("/players/%s", name), nil)
	return req
}

func newPostWinRequest(name string) *http. Request {
	req, _ := http. NewRequest(http. MethodPost, fmt. Sprintf("/players/%s", name), nil)
	return req
}

func assertResponseBody(t *testing. T, got, want string) {
	t.Helper()
	if got ! = want {
		t.Errorf("response body is wrong, got %q want %q", got, want)
	}
}
