package main

import (
	"fmt"
	"github.com/gavv/httpexpect/v2"
	"strconv"
	"testing"
	"time"
)

const (
	TestGoalName = "Test Goal"
)

func startServer(t *testing.T) *httpexpect.Expect {
	config := ServerConfig{
		Datasource: ":memory:",
		Addr:       ":0",
		Started:    make(chan bool),
	}
	go start(t.Context(), &config)
	<-config.Started
	return httpexpect.Default(t, fmt.Sprintf("http://localhost:%d/api", config.Port))
}

func TestGetUsers(t *testing.T) {
	e := startServer(t)
	result := e.GET("/users").Expect().Status(200).JSON()
	result.Array().Length().IsEqual(2)
	result.Path("$[0].name").InList("Seeseemelk", "Huffle")
	result.Path("$[1].name").InList("Seeseemelk", "Huffle")
}

func TestGetUser(t *testing.T) {
	e := startServer(t)
	result := e.GET("/user/1").Expect().Status(200).JSON().Object()
	result.Value("name").IsEqual("Seeseemelk")
	result.Value("id").IsEqual(1)
	result.Value("allowance").IsEqual(0)
}

func TestGetUserUnknown(t *testing.T) {
	e := startServer(t)
	e.GET("/user/999").Expect().Status(404)
}

func TestGetUserBadId(t *testing.T) {
	e := startServer(t)
	e.GET("/user/bad-id").Expect().Status(400)
}

func TestGetUserGoalsWhenNoGoalsPresent(t *testing.T) {
	e := startServer(t)
	result := e.GET("/user/1/goals").Expect().Status(200).JSON().Array()
	result.Length().IsEqual(0)
}

func TestGetUserGoals(t *testing.T) {
	e := startServer(t)

	// Create a new goal
	requestBody := map[string]interface{}{
		"name":   TestGoalName,
		"target": 5000,
		"weight": 10,
	}
	e.POST("/user/1/goals").WithJSON(requestBody).Expect().Status(201)

	// Validate goal
	result := e.GET("/user/1/goals").Expect().Status(200).JSON().Array()
	result.Length().IsEqual(1)
	item := result.Value(0).Object()
	item.Value("id").IsEqual(1)
	item.Value("name").IsEqual(TestGoalName)
	item.Value("target").IsEqual(5000)
	item.Value("weight").IsEqual(10)
	item.Value("progress").IsEqual(0)
	item.NotContainsKey("user_id")
}

func TestGetUserGoalsNoUser(t *testing.T) {
	e := startServer(t)
	e.GET("/user/999/goals").Expect().Status(404)
}

func TestGetUserGoalsBadId(t *testing.T) {
	e := startServer(t)
	e.GET("/user/bad-id/goals").Expect().Status(400)
}

func TestCreateUserGoal(t *testing.T) {
	e := startServer(t)

	// Create a new goal
	requestBody := map[string]interface{}{
		"name":   TestGoalName,
		"target": 5000,
		"weight": 10,
	}

	response := e.POST("/user/1/goals").
		WithJSON(requestBody).
		Expect().
		Status(201).
		JSON().Object()

	// Verify the response has an ID
	response.ContainsKey("id")
	goalId := response.Value("id").Number().Raw()

	// Verify the goal exists in the list of goals
	goals := e.GET("/user/1/goals").
		Expect().
		Status(200).
		JSON().Array()

	goals.Length().IsEqual(1)

	goal := goals.Value(0).Object()
	goal.Value("id").IsEqual(goalId)
	goal.Value("name").IsEqual(TestGoalName)
	goal.Value("target").IsEqual(5000)
	goal.Value("weight").IsEqual(10)
	goal.Value("progress").IsEqual(0)
}

func TestCreateUserGoalNoUser(t *testing.T) {
	e := startServer(t)

	requestBody := map[string]interface{}{
		"name":   TestGoalName,
		"target": 5000,
		"weight": 10,
	}

	e.POST("/user/999/goals").
		WithJSON(requestBody).
		Expect().
		Status(404)
}

func TestCreateUserGoalInvalidInput(t *testing.T) {
	e := startServer(t)

	// Test with empty name
	requestBody := map[string]interface{}{
		"name":   "",
		"target": 5000,
		"weight": 10,
	}

	e.POST("/user/1/goals").
		WithJSON(requestBody).
		Expect().
		Status(400)

	// Test with missing fields
	invalidRequest := map[string]interface{}{
		"target": 5000,
	}

	e.POST("/user/1/goals").
		WithJSON(invalidRequest).
		Expect().
		Status(400)
}

func TestCreateUserGoalBadId(t *testing.T) {
	e := startServer(t)

	requestBody := map[string]interface{}{
		"name":   TestGoalName,
		"target": 5000,
		"weight": 10,
	}

	e.POST("/user/bad-id/goals").
		WithJSON(requestBody).
		Expect().
		Status(400)
}

func TestDeleteUserGoal(t *testing.T) {
	e := startServer(t)

	// Create a new goal to delete
	createRequest := map[string]interface{}{
		"name":   TestGoalName,
		"target": 1000,
		"weight": 5,
	}
	response := e.POST("/user/1/goals").
		WithJSON(createRequest).
		Expect().
		Status(201).
		JSON().Object()

	goalId := response.Value("id").Number().Raw()

	// Delete the goal
	e.DELETE("/user/1/goal/" + strconv.Itoa(int(goalId))).
		Expect().
		Status(200).
		JSON().Object().Value("message").IsEqual("Goal deleted successfully")

	// Verify the goal no longer exists
	goals := e.GET("/user/1/goals").
		Expect().
		Status(200).
		JSON().Array()
	goals.Length().IsEqual(0)
}

func TestDeleteUserGoalNotFound(t *testing.T) {
	e := startServer(t)

	// Attempt to delete a non-existent goal
	e.DELETE("/user/1/goal/999").
		Expect().
		Status(404).
		JSON().Object().Value("error").IsEqual("Goal not found")
}

func TestDeleteUserGoalInvalidId(t *testing.T) {
	e := startServer(t)

	// Attempt to delete a goal with an invalid ID
	e.DELETE("/user/1/goal/invalid-id").
		Expect().
		Status(400).
		JSON().Object().Value("error").IsEqual("Invalid goal ID")
}

func TestCreateTask(t *testing.T) {
	e := startServer(t)

	// Create a new task without assigned user
	requestBody := map[string]interface{}{
		"name":   "Test Task",
		"reward": 100,
	}

	response := e.POST("/tasks").
		WithJSON(requestBody).
		Expect().
		Status(201). // Expect Created status
		JSON().Object()

	// Verify the response has an ID
	response.ContainsKey("id")
	taskId := response.Value("id").Number().Raw()

	// Create a new task with assigned user
	assignedUserId := 1
	requestBodyWithUser := map[string]interface{}{
		"name":     "Test Task Assigned",
		"reward":   200,
		"assigned": assignedUserId,
	}

	responseWithUser := e.POST("/tasks").
		WithJSON(requestBodyWithUser).
		Expect().
		Status(201).
		JSON().Object()

	responseWithUser.ContainsKey("id")
	responseWithUser.Value("id").Number().NotEqual(taskId) // Ensure different ID
}

func TestCreateTaskNoName(t *testing.T) {
	e := startServer(t)

	requestBody := map[string]interface{}{
		"reward": 100,
	}

	e.POST("/tasks").WithJSON(requestBody).Expect().Status(400)
}

func TestCreateTaskInvalidAssignedUser(t *testing.T) {
	e := startServer(t)

	requestBody := map[string]interface{}{
		"name":     "Test Task Invalid User",
		"reward":   100,
		"assigned": 999, // Non-existent user ID
	}

	e.POST("/tasks").
		WithJSON(requestBody).
		Expect().
		Status(404). // Expect Not Found
		JSON().Object().Value("error").IsEqual(ErrUserNotFound)
}

func TestCreateTaskInvalidRequestBody(t *testing.T) {
	e := startServer(t)

	// Test with missing fields (name is required)
	invalidRequest := map[string]interface{}{
		"reward": 5000,
	}

	e.POST("/tasks").
		WithJSON(invalidRequest).
		Expect().
		Status(400)
}

func TestGetTaskWhenNoTasks(t *testing.T) {
	e := startServer(t)

	result := e.GET("/tasks").Expect().Status(200).JSON().Array()
	result.Length().IsEqual(0)
}

func createTestTask(e *httpexpect.Expect) {
	requestBody := map[string]interface{}{
		"name":   "Test Task",
		"reward": 100,
	}
	e.POST("/tasks").WithJSON(requestBody).Expect().Status(201)
}

func TestGetTaskSWhenTasks(t *testing.T) {
	e := startServer(t)
	createTestTask(e)

	// Get the task
	result := e.GET("/tasks").Expect().Status(200).JSON().Array()
	result.Length().IsEqual(1)
	item := result.Value(0).Object()
	item.Value("id").IsEqual(1)
	item.Value("name").IsEqual("Test Task")
	item.Value("reward").IsEqual(100)
	item.Value("assigned").IsNull()
}

func TestGetTask(t *testing.T) {
	e := startServer(t)
	createTestTask(e)

	result := e.GET("/task/1").Expect().Status(200).JSON().Object()
	result.Value("id").IsEqual(1)
	result.Value("name").IsEqual("Test Task")
	result.Value("reward").IsEqual(100)
	result.Value("assigned").IsNull()
}

func TestGetTaskInvalidId(t *testing.T) {
	e := startServer(t)
	createTestTask(e)
	e.GET("/task/2").Expect().Status(404)
}

func TestGetTaskBadId(t *testing.T) {
	e := startServer(t)
	createTestTask(e)
	e.GET("/task/invalid").Expect().Status(400)
}

func TestPutTaskModifiesTask(t *testing.T) {
	e := startServer(t)
	createTestTask(e)
	requestBody := map[string]interface{}{
		"name":   "Updated Task",
		"reward": 100,
	}
	e.PUT("/task/1").WithJSON(requestBody).Expect().
		Status(200).
		JSON().Object()

	// Verify the task is updated
	result := e.GET("/task/1").Expect().Status(200).JSON().Object()
	result.Value("id").IsEqual(1)
	result.Value("name").IsEqual("Updated Task")
	result.Value("reward").IsEqual(100)
}

func TestPutTaskInvalidTaskId(t *testing.T) {
	e := startServer(t)
	createTestTask(e)

	requestBody := map[string]interface{}{
		"name": "Updated Task",
	}
	e.PUT("/task/999").WithJSON(requestBody).Expect().Status(404)
}

func TestPostAllowance(t *testing.T) {
	e := startServer(t)

	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect().Status(200)
	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 20}).Expect().Status(200)
	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: -10}).Expect().Status(200)

	response := e.GET("/user/1").Expect().Status(200).JSON().Object()
	response.Value("allowance").Number().IsEqual(100 + 20 - 10)
}

func TestPostAllowanceInvalidUserId(t *testing.T) {
	e := startServer(t)

	e.POST("/user/999/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect().
		Status(404)
}

func TestGetAllowance(t *testing.T) {
	e := startServer(t)

	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 100}).Expect().Status(200)
	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: 20}).Expect().Status(200)
	e.POST("/user/1/allowance").WithJSON(PostAllowance{Allowance: -10}).Expect().Status(200)

	response := e.GET("/user/1/allowance").Expect().Status(200).JSON().Array()
	response.Length().IsEqual(3)
	response.Value(0).Object().Value("allowance").Number().IsEqual(100)
	response.Value(0).Object().Value("timestamp").Number().NotEqual(0).InDelta(float64(time.Now().Unix()), 2.0)
	response.Value(1).Object().Value("allowance").Number().IsEqual(20)
	response.Value(2).Object().Value("allowance").Number().IsEqual(-10)
}