package handlers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
	"github.com/remogatto/prettytest"
	"gogs.carducci-dante.gov.it/karmen/core/config"
	"gogs.carducci-dante.gov.it/karmen/core/orm"
	"gogs.carducci-dante.gov.it/karmen/core/renderer"
)

var (
	token string
)

// Start of setup

type testSuite struct {
	prettytest.Suite
}

func TestRunner(t *testing.T) {
	prettytest.Run(
		t,
		new(testSuite),
	)
}

func (t *testSuite) BeforeAll() {

	var (
		db  *gorm.DB
		err error
	)

	// Initialize the ORM

	connected := false
	for !connected {
		time.Sleep(10 * time.Second)
		db, err = orm.New("karmen:karmen@/karmen_test?charset=utf8&parseTime=True&loc=Local")
		if err != nil {
			time.Sleep(5 * time.Second)
			continue
		}
		connected = true
	}

	orm.Use(db)
	orm.AutoMigrate()

	// Initialize the renderers

	htmlRenderer, err := renderer.NewHTMLRenderer("./testdata/templates/")
	if err != nil {
		panic(err)
	}

	jsonRenderer, err := renderer.NewJSONRenderer()
	if err != nil {
		panic(err)
	}

	csvRenderer, err := renderer.NewCSVRenderer()
	if err != nil {
		panic(err)
	}

	renderer.Render = make(map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values))

	renderer.Render["html"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
		htmlRenderer.Render(w, r, data, options...)
	}

	renderer.Render["json"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
		jsonRenderer.Render(w, r, data, options...)
	}

	renderer.Render["csv"] = func(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) {
		csvRenderer.Render(w, r, data, options...)
	}

	// Load the configuration

	err = config.ReadFile("testdata/config.yaml", config.Config)
	if err != nil {
		panic(err)
	}
	config.Config.LogLevel = config.LOG_LEVEL_OFF

	req, err := http.NewRequest("GET", "/get_token", nil)
	if err != nil {
		panic(err)
	}

	req.SetBasicAuth("admin", "admin")

	rr := httptest.NewRecorder()
	tokenHandler().ServeHTTP(rr, req)

	var data struct {
		Token  string
		UserID string
	}

	if err := json.Unmarshal(rr.Body.Bytes(), &data); err != nil {
		panic(err)
	}

	token = data.Token

}

func (t *testSuite) TestGetTeachersHTML() {

	req, err := http.NewRequest("GET", "/teachers?format=html&tpl_layout=base&tpl_content=teachers", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{
		"/%s",
		"/%s?format=html&tpl_layout=base&tpl_content=%s",
		[]string{"GET"},
	}

	rr := httptest.NewRecorder()
	modelHandler("teachers", pattern).ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		t.True(strings.Contains(rr.Body.String(), "DELLE ROSE"))
	}

}

func (t *testSuite) TestGetTeachersJSON() {
	var (
		teachers []*orm.Teacher
		response renderer.JsonResponse
	)

	req, err := http.NewRequest("GET", "/api/teachers?format=json", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{
		"/api/%s",
		"/%s/%d?format=json",
		[]string{"GET"},
	}

	rr := httptest.NewRecorder()
	modelHandler("teachers", pattern).ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		err := json.Unmarshal(rr.Body.Bytes(), &response)
		t.Nil(err)
		if !t.Failed() {
			err := json.Unmarshal(response.Result, &teachers)
			t.Nil(err)
			t.Equal("AGOSTINO", teachers[0].Surname)
		}
	}

}

func (t *testSuite) TestDeleteActivityJSON() {
	req, err := http.NewRequest("DELETE", fmt.Sprintf("/api/activities/%d/delete?format=json", 1), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	rr := httptest.NewRecorder()

	pattern := PathPattern{"/api/%s/{id}/delete", "", []string{"DELETE"}}
	router := mux.NewRouter()
	router.Handle("/api/activities/{id}/delete", modelHandler("activities", pattern))
	router.ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)
}

func (t *testSuite) TestGetTeacherJSON() {
	var (
		teacher  *orm.Teacher
		response renderer.JsonResponse
	)

	req, err := http.NewRequest("GET", "/api/teachers/9?format=json", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{"/api/%s/{id}", "", []string{"GET"}}

	rr := httptest.NewRecorder()

	router := mux.NewRouter()
	router.Handle("/api/teachers/{id}", modelHandler("teachers", pattern))
	router.ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		err := json.Unmarshal(rr.Body.Bytes(), &response)
		t.Nil(err)
		if !t.Failed() {
			err := json.Unmarshal(response.Result, &teacher)
			t.Nil(err)
			t.Equal("FRANCESCHINI", teacher.Surname)
		}
	}

}

func (t *testSuite) TestGetTeachersCSV() {
	var response renderer.JsonResponse

	req, err := http.NewRequest("GET", "/api/teachers?format=csv", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{
		"/api/%s",
		"/%s/%d?format=csv",
		[]string{"GET"},
	}

	rr := httptest.NewRecorder()
	modelHandler("teachers", pattern).ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		err := json.Unmarshal(rr.Body.Bytes(), &response)
		t.Nil(err)
		if !t.Failed() {
			t.True(strings.Contains(string(response.Result), "AGOSTINO"))
		}
	}

}

func (t *testSuite) TestGetErrorJSON() {
	var (
		response renderer.JsonResponse
	)

	req, err := http.NewRequest("GET", "/api/teacher/100?format=json", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{"/api/%s/{id}", "/%s?format=json", []string{"GET"}}

	rr := httptest.NewRecorder()
	modelHandler("teachers", pattern).ServeHTTP(rr, req)

	err = json.Unmarshal(rr.Body.Bytes(), &response)
	t.Nil(err)
	t.Equal("record not found", string(response.Error))

}

func (t *testSuite) TestPostErrorJSON() {
	var (
		response renderer.JsonResponse
	)

	teacher := getTeacherJSON(1)
	teacher.Name = "Mario"
	teacher.Surname = "ROSSI"

	data, err := json.Marshal(teacher)
	if err != nil {
		panic(err)
	}

	req, err := http.NewRequest("POST", "/api/teachers/0/update?format=json", bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	rr := httptest.NewRecorder()

	pattern := PathPattern{"/api/%s/{id}/update", "", []string{"POST"}}
	router := mux.NewRouter()
	router.Handle("/api/teachers/{id}/update", modelHandler("teachers", pattern))
	router.ServeHTTP(rr, req)

	t.Equal(http.StatusInternalServerError, rr.Code)

	err = json.Unmarshal(rr.Body.Bytes(), &response)
	t.Nil(err)
	t.Equal("record not found", string(response.Error))

}

func (t *testSuite) TestAddTeacherJSON() {
	var (
		response renderer.JsonResponse
		id       uint
	)

	teacher := new(orm.Teacher)
	teacher.Name = "Mario"
	teacher.Surname = "ROSSI"

	data, err := json.Marshal(teacher)
	t.Nil(err)

	req, err := http.NewRequest("POST", "/api/teachers/add", bytes.NewBuffer(data))
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	if err != nil {
		panic(err)
	}

	pattern := PathPattern{
		"/api/%s/add",
		"",
		[]string{"POST"},
	}

	rr := httptest.NewRecorder()
	modelHandler("teachers", pattern).ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		err := json.Unmarshal(rr.Body.Bytes(), &response)
		t.Nil(err)
		if !t.Failed() {
			err := json.Unmarshal(response.Result, &id)
			t.Nil(err)
			t.Equal(uint(10), id)
		}
	}

}

func (t *testSuite) TestUpdateTeacherJSON() {
	teacher := getTeacherJSON(1)
	teacher.Name = "Mario"
	teacher.Surname = "ROSSI"

	data, err := json.Marshal(teacher)
	if err != nil {
		panic(err)
	}

	req, err := http.NewRequest("POST", fmt.Sprintf("/api/teachers/%d/update?format=json", teacher.ID), bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json; charset=utf-8")

	rr := httptest.NewRecorder()

	pattern := PathPattern{"/api/%s/{id}/update", "", []string{"POST"}}
	router := mux.NewRouter()
	router.Handle("/api/teachers/{id}/update", modelHandler("teachers", pattern))
	router.ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		dbTeacher := getTeacherJSON(1)
		t.Equal("ROSSI", dbTeacher.Surname)
	}

}

func getTeacherJSON(id uint) *orm.Teacher {
	var (
		teacher  *orm.Teacher
		response renderer.JsonResponse
	)

	req, err := http.NewRequest("GET", fmt.Sprintf("/api/teachers/%d?format=json", id), nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{"/api/%s/{id}", "", []string{"GET"}}

	rr := httptest.NewRecorder()

	router := mux.NewRouter()
	router.Handle("/api/teachers/{id}", modelHandler("teachers", pattern))
	router.ServeHTTP(rr, req)

	err = json.Unmarshal(rr.Body.Bytes(), &response)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(response.Result, &teacher)
	if err != nil {
		panic(err)
	}
	return teacher

}

func (t *testSuite) TestGetDepartmentJSON() {
	var (
		department *orm.Department
		response   renderer.JsonResponse
	)

	req, err := http.NewRequest("GET", "/api/departments/1?format=json", nil)
	if err != nil {
		panic(err)
	}

	pattern := PathPattern{"/api/%s/{id}", "", []string{"GET"}}

	rr := httptest.NewRecorder()

	router := mux.NewRouter()
	router.Handle("/api/departments/{id}", modelHandler("departments", pattern))
	router.ServeHTTP(rr, req)

	t.Equal(http.StatusOK, rr.Code)

	if !t.Failed() {
		err := json.Unmarshal(rr.Body.Bytes(), &response)
		t.Nil(err)
		if !t.Failed() {
			err := json.Unmarshal(response.Result, &department)
			t.Nil(err)
			t.Equal("LINGUE STRANIERE", department.Name)
		}
	}

}
