package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestJobsHandler_CreateJob(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	jobReq := map[string]interface{}{
		"type": "import",
		"params": map[string]interface{}{
			"test": "data",
		},
	}

	req, err := http.NewRequest("POST", setup.Server.URL+"/api/jobs", bytes.NewBuffer(jsonMarshal(jobReq)))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusAccepted, resp.StatusCode)

	var response map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&response)
	require.NoError(t, err)

	assert.NotEmpty(t, response["job_id"])
	assert.Equal(t, "Job created", response["message"])
	assert.Equal(t, "import", response["type"])
	assert.Equal(t, "pending", response["status"])
}

func TestJobsHandler_CreateJob_InvalidType(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	jobReq := map[string]interface{}{
		"type":   "invalid_job_type",
		"params": map[string]interface{}{},
	}

	req, err := http.NewRequest("POST", setup.Server.URL+"/api/jobs", bytes.NewBuffer(jsonMarshal(jobReq)))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}

func TestJobsHandler_GetJobStatus(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	jobReq := map[string]interface{}{
		"type":   "import",
		"params": map[string]interface{}{},
	}

	createReq, err := http.NewRequest("POST", setup.Server.URL+"/api/jobs", bytes.NewBuffer(jsonMarshal(jobReq)))
	require.NoError(t, err)
	createReq.Header.Set("Content-Type", "application/json")
	createReq.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createResp, err := client.Do(createReq)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(createResp.Body)

	var createResponse map[string]interface{}
	err = json.NewDecoder(createResp.Body).Decode(&createResponse)
	require.NoError(t, err)

	jobID := createResponse["job_id"].(string)
	require.NotEmpty(t, jobID)

	getReq, err := http.NewRequest("GET", fmt.Sprintf("%s/api/jobs/%s", setup.Server.URL, jobID), nil)
	require.NoError(t, err)
	getReq.Header.Set("Authorization", "Bearer "+token)

	getResp, err := client.Do(getReq)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(getResp.Body)

	require.Equal(t, http.StatusOK, getResp.StatusCode)

	var statusResponse map[string]interface{}
	err = json.NewDecoder(getResp.Body).Decode(&statusResponse)
	require.NoError(t, err)

	assert.Equal(t, jobID, statusResponse["job_id"])
}

func TestJobsHandler_GetJobStatus_NotFound(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	fakeJobID := uuid.New().String()

	req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/jobs/%s", setup.Server.URL, fakeJobID), nil)
	require.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestJobsHandler_CreateAndTrackJob(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	jobReq := map[string]interface{}{
		"type":   "import",
		"params": map[string]interface{}{},
	}

	createReq, err := http.NewRequest("POST", setup.Server.URL+"/api/jobs", bytes.NewBuffer(jsonMarshal(jobReq)))
	require.NoError(t, err)
	createReq.Header.Set("Content-Type", "application/json")
	createReq.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createResp, err := client.Do(createReq)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(createResp.Body)

	var createResponse map[string]interface{}
	err = json.NewDecoder(createResp.Body).Decode(&createResponse)
	require.NoError(t, err)

	jobID := createResponse["job_id"].(string)

	var finalStatus string
	for i := 0; i < 20; i++ {
		time.Sleep(500 * time.Millisecond)

		getReq, err := http.NewRequest("GET", fmt.Sprintf("%s/api/jobs/%s", setup.Server.URL, jobID), nil)
		require.NoError(t, err)
		getReq.Header.Set("Authorization", "Bearer "+token)

		getResp, err := client.Do(getReq)
		require.NoError(t, err)

		var statusResponse map[string]interface{}
		err = json.NewDecoder(getResp.Body).Decode(&statusResponse)
		_ = getResp.Body.Close()
		require.NoError(t, err)

		if statusResponse["status"] != nil {
			finalStatus = statusResponse["status"].(string)
			if finalStatus == "completed" || finalStatus == "failed" {
				break
			}
		}
	}

	assert.True(t, finalStatus == "completed" || finalStatus == "failed",
		fmt.Sprintf("Job should complete, got status: %s", finalStatus))
}

func TestJobsHandler_CreateJob_Unauthorized(t *testing.T) {
	setup := setupTestServer(t)

	jobReq := map[string]interface{}{
		"type":   "import",
		"params": map[string]interface{}{},
	}

	req, err := http.NewRequest("POST", setup.Server.URL+"/api/jobs", bytes.NewBuffer(jsonMarshal(jobReq)))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}

func TestJobsHandler_GetJobStatus_Unauthorized(t *testing.T) {
	setup := setupTestServer(t)

	jobID := uuid.New().String()

	req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/jobs/%s", setup.Server.URL, jobID), nil)
	require.NoError(t, err)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}
