package main

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

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

// TestRefreshTokenFlow comprehensive tests for token refresh functionality
func TestRefreshTokenFlow(t *testing.T) {
	setup := setupTestServer(t)
	client := &http.Client{}

	t.Run("RefreshToken_MissingToken", func(t *testing.T) {
		req := map[string]interface{}{}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("RefreshToken_InvalidTokenFormat", func(t *testing.T) {
		req := map[string]interface{}{
			"refresh_token": "not-a-valid-jwt-token",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("RefreshToken_ValidToken", func(t *testing.T) {
		// First, login to get tokens
		loginReq := map[string]string{
			"login":    "testuser@tests.bookhoard.internal",
			"password": "Test@Pass123!",
		}

		loginBody, _ := json.Marshal(loginReq)
		loginHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/login", bytes.NewBuffer(loginBody))
		loginHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		refreshToken, ok := loginResult["refresh_token"].(string)
		require.True(t, ok, "Should have refresh_token")

		// Now use the refresh token
		refreshReq := map[string]interface{}{
			"refresh_token": refreshToken,
		}
		refreshBody, _ := json.Marshal(refreshReq)

		refreshHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(refreshBody))
		refreshHTTP.Header.Set("Content-Type", "application/json")

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

		assert.Equal(t, http.StatusOK, refreshResp.StatusCode)

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

		assert.Contains(t, refreshResult, "access_token")
		assert.NotEmpty(t, refreshResult["access_token"], "New access token should not be empty")

		// The new access token should be different from the original
		newAccessToken := refreshResult["access_token"].(string)
		originalAccessToken := loginResult["access_token"].(string)
		assert.NotEqual(t, originalAccessToken, newAccessToken, "New access token should be different")
	})

	t.Run("RefreshToken_InvalidRequestBody", func(t *testing.T) {
		// Send invalid JSON
		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer([]byte("invalid json")))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("RefreshToken_MissingContentType", func(t *testing.T) {
		req := map[string]interface{}{
			"refresh_token": "some-token",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(body))
		// Don't set Content-Type

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

		// Should still work or return appropriate error
		assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnsupportedMediaType)
	})
}

// TestRefreshTokenSecurity tests security aspects of token refresh
func TestRefreshTokenSecurity(t *testing.T) {
	setup := setupTestServer(t)
	client := &http.Client{}

	t.Run("RefreshToken_ReuseProtection", func(t *testing.T) {
		// Login to get tokens
		loginReq := map[string]string{
			"login":    "testuser@tests.bookhoard.internal",
			"password": "Test@Pass123!",
		}

		loginBody, _ := json.Marshal(loginReq)
		loginHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/login", bytes.NewBuffer(loginBody))
		loginHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		refreshToken := loginResult["refresh_token"].(string)

		// Use the refresh token first time
		refreshReq := map[string]interface{}{
			"refresh_token": refreshToken,
		}
		refreshBody, _ := json.Marshal(refreshReq)

		refreshHTTP1, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(refreshBody))
		refreshHTTP1.Header.Set("Content-Type", "application/json")

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

		assert.Equal(t, http.StatusOK, refreshResp1.StatusCode)

		// Try to reuse the same refresh token (should fail if refresh token rotation is enabled)
		refreshHTTP2, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(refreshBody))
		refreshHTTP2.Header.Set("Content-Type", "application/json")

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

		// May return 401 if token reuse is detected, or 200 if not implemented
		// Either is acceptable depending on security requirements
		assert.True(t, refreshResp2.StatusCode == http.StatusOK || refreshResp2.StatusCode == http.StatusUnauthorized)
	})
}

// TestRefreshTokenEdgeCases tests edge cases for token refresh
func TestRefreshTokenEdgeCases(t *testing.T) {
	setup := setupTestServer(t)
	client := &http.Client{}

	t.Run("RefreshToken_EmptyStringToken", func(t *testing.T) {
		req := map[string]interface{}{
			"refresh_token": "",
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("RefreshToken_NullToken", func(t *testing.T) {
		req := map[string]interface{}{
			"refresh_token": nil,
		}
		body, _ := json.Marshal(req)

		httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(body))
		httpReq.Header.Set("Content-Type", "application/json")

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

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

	t.Run("RefreshToken_ResponseStructure", func(t *testing.T) {
		// Login to get tokens
		loginReq := map[string]string{
			"login":    "testuser@tests.bookhoard.internal",
			"password": "Test@Pass123!",
		}

		loginBody, _ := json.Marshal(loginReq)
		loginHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/login", bytes.NewBuffer(loginBody))
		loginHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		refreshToken := loginResult["refresh_token"].(string)

		// Refresh the token
		refreshReq := map[string]interface{}{
			"refresh_token": refreshToken,
		}
		refreshBody, _ := json.Marshal(refreshReq)

		refreshHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(refreshBody))
		refreshHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		// Verify response structure
		assert.Contains(t, refreshResult, "access_token")
		assert.NotEmpty(t, refreshResult["access_token"])

		// May or may not contain refresh_token (if rotation is enabled)
		// Both are valid responses
	})

	t.Run("RefreshToken_TokenType", func(t *testing.T) {
		// Login to get tokens
		loginReq := map[string]string{
			"login":    "testuser@tests.bookhoard.internal",
			"password": "Test@Pass123!",
		}

		loginBody, _ := json.Marshal(loginReq)
		loginHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/login", bytes.NewBuffer(loginBody))
		loginHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		refreshToken := loginResult["refresh_token"].(string)

		// Refresh the token
		refreshReq := map[string]interface{}{
			"refresh_token": refreshToken,
		}
		refreshBody, _ := json.Marshal(refreshReq)

		refreshHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/auth/refresh", bytes.NewBuffer(refreshBody))
		refreshHTTP.Header.Set("Content-Type", "application/json")

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

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

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

		// Verify access token is a string
		accessToken, ok := refreshResult["access_token"].(string)
		assert.True(t, ok, "access_token should be a string")
		assert.NotEmpty(t, accessToken, "access_token should not be empty")
		assert.Greater(t, len(accessToken), 20, "access_token should be a reasonably long JWT")
	})
}
