package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

// TestUpdateUserMaxDevices tests the admin endpoint for updating user device cap
func TestUpdateUserMaxDevices(t *testing.T) {
	setup := setupTestServer(t)
	adminToken := setup.Token

	// Create a test user
	userID := createTestUserForMaxDevices(t, setup.Server, adminToken)

	tests := []struct {
		name           string
		maxDevices     int32
		expectedStatus int
		expectedBody   string
	}{
		{
			name:           "Update to 5 devices",
			maxDevices:     5,
			expectedStatus: http.StatusOK,
			expectedBody:   `{"message":"max devices updated"}`,
		},
		{
			name:           "Update to 10 devices (default)",
			maxDevices:     10,
			expectedStatus: http.StatusOK,
			expectedBody:   `{"message":"max devices updated"}`,
		},
		{
			name:           "Update to 50 devices",
			maxDevices:     50,
			expectedStatus: http.StatusOK,
			expectedBody:   `{"message":"max devices updated"}`,
		},
		{
			name:           "Update to 100 devices (maximum)",
			maxDevices:     100,
			expectedStatus: http.StatusOK,
			expectedBody:   `{"message":"max devices updated"}`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			payload := map[string]interface{}{
				"max_devices": tt.maxDevices,
			}
			jsonData, _ := json.Marshal(payload)

			req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
			req.Header.Set("Content-Type", "application/json")
			req.Header.Set("Authorization", "Bearer "+adminToken)

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

			assert.Equal(t, tt.expectedStatus, resp.StatusCode, "expected status code")

			body := new(bytes.Buffer)
			body.ReadFrom(resp.Body)
			// Trim trailing whitespace/newline from response body
			assert.Equal(t, tt.expectedBody, strings.TrimSpace(body.String()), "expected response body")
		})
	}
}

// TestUpdateUserMaxDevicesValidation tests validation of max_devices parameter
func TestUpdateUserMaxDevicesValidation(t *testing.T) {
	setup := setupTestServer(t)

	// Create admin user and get token
	adminToken := setup.Token
	createAdminUser(t, setup.Server, adminToken)
	adminUserID := getTestUserID(t, setup.DB)
	adminToken = getAdminToken(t, setup.Server, adminUserID)

	// Create test user
	userID := createTestUserForMaxDevices(t, setup.Server, adminToken)

	tests := []struct {
		name           string
		maxDevices     int32
		expectedStatus int
	}{
		{
			name:           "Zero devices (below minimum)",
			maxDevices:     0,
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "Negative devices",
			maxDevices:     -1,
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "101 devices (above maximum)",
			maxDevices:     101,
			expectedStatus: http.StatusBadRequest,
		},
		{
			name:           "1000 devices (far above maximum)",
			maxDevices:     1000,
			expectedStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			payload := map[string]interface{}{
				"max_devices": tt.maxDevices,
			}
			jsonData, _ := json.Marshal(payload)

			req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
			req.Header.Set("Content-Type", "application/json")
			req.Header.Set("Authorization", "Bearer "+adminToken)

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

			assert.Equal(t, tt.expectedStatus, resp.StatusCode, "expected validation error")
		})
	}
}

// TestUpdateUserMaxDevicesAuth tests authentication requirements
func TestUpdateUserMaxDevicesAuth(t *testing.T) {
	setup := setupTestServer(t)

	// Create admin user
	adminToken := setup.Token
	createAdminUser(t, setup.Server, adminToken)
	adminUserID := getTestUserID(t, setup.DB)
	adminToken = getAdminToken(t, setup.Server, adminUserID)

	// Create regular user
	userID := createTestUserForMaxDevices(t, setup.Server, adminToken)

	t.Run("No authorization", func(t *testing.T) {
		payload := map[string]interface{}{
			"max_devices": 10,
		}
		jsonData, _ := json.Marshal(payload)

		req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
		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)

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

	t.Run("Non-admin user", func(t *testing.T) {
		// Create another regular user and get their token
		_ = createTestUserForMaxDevices(t, setup.Server, adminToken)
		// Login as the maxdevices user (who is a regular user, not admin)
		regularToken := loginTestUserByCredentials(t, setup.Server, "maxdevices@example.com", "Test@Pass123!")
		require.NotEmpty(t, regularToken, "Failed to login as regular user")

		payload := map[string]interface{}{
			"max_devices": 10,
		}
		jsonData, _ := json.Marshal(payload)

		req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+userID.String()+"/max-devices", bytes.NewBuffer(jsonData))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+regularToken)

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

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

// TestUpdateUserMaxDevicesNonExistentUser tests with non-existent user ID
func TestUpdateUserMaxDevicesNonExistentUser(t *testing.T) {
	setup := setupTestServer(t)

	// Create admin user
	adminToken := setup.Token
	createAdminUser(t, setup.Server, adminToken)
	adminUserID := getTestUserID(t, setup.DB)
	adminToken = getAdminToken(t, setup.Server, adminUserID)

	// Use a non-existent user ID
	nonExistentUserID := uuid.New()

	payload := map[string]interface{}{
		"max_devices": 10,
	}
	jsonData, _ := json.Marshal(payload)

	req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users/"+nonExistentUserID.String()+"/max-devices", bytes.NewBuffer(jsonData))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+adminToken)

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

	// Should return 500 or 404 depending on implementation
	assert.True(t, resp.StatusCode == http.StatusInternalServerError || resp.StatusCode == http.StatusNotFound)
}

// TestUpdateUserMaxDevicesMissingUserID tests with missing user ID in URL
func TestUpdateUserMaxDevicesMissingUserID(t *testing.T) {
	setup := setupTestServer(t)

	// Create admin user
	adminToken := setup.Token
	createAdminUser(t, setup.Server, adminToken)
	adminUserID := getTestUserID(t, setup.DB)
	adminToken = getAdminToken(t, setup.Server, adminUserID)

	payload := map[string]interface{}{
		"max_devices": 10,
	}
	jsonData, _ := json.Marshal(payload)

	// Missing user ID in URL
	req, _ := http.NewRequest("PUT", setup.Server.URL+"/api/auth/users//max-devices", bytes.NewBuffer(jsonData))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+adminToken)

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

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

// TestListUsersIncludesMaxDevices tests that List Users returns max_devices field
func TestListUsersIncludesMaxDevices(t *testing.T) {
	setup := setupTestServer(t)

	// Create admin user
	adminToken := setup.Token
	createAdminUser(t, setup.Server, adminToken)
	adminUserID := getTestUserID(t, setup.DB)
	adminToken = getAdminToken(t, setup.Server, adminUserID)

	req, _ := http.NewRequest("GET", setup.Server.URL+"/api/auth/users", nil)
	req.Header.Set("Authorization", "Bearer "+adminToken)

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

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

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

	// Verify max_devices and device_count fields are present in response
	if len(users) > 0 {
		_, hasMaxDevices := users[0]["max_devices"]
		assert.True(t, hasMaxDevices, "max_devices field should be present in user list")

		_, hasDeviceCount := users[0]["device_count"]
		assert.True(t, hasDeviceCount, "device_count field should be present in user list")
	}
}

// Helper function to create admin user
func createAdminUser(t *testing.T, ts *httptest.Server, token string) {
	createUserPayload := map[string]interface{}{
		"email":      "admin@example.com",
		"username":   "adminuser",
		"password":   "Admin@Pass123!",
		"first_name": "Admin",
		"last_name":  "User",
		"role":       "admin",
	}

	jsonData, _ := json.Marshal(createUserPayload)
	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/register", bytes.NewBuffer(jsonData))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	resp, _ := client.Do(req)
	_ = resp.Body.Close()
}

// Helper function to create test user for max devices tests
func createTestUserForMaxDevices(t *testing.T, ts *httptest.Server, adminToken string) uuid.UUID {
	createUserPayload := map[string]interface{}{
		"email":      "maxdevices@example.com",
		"username":   "maxdevicesuser",
		"password":   "Test@Pass123!",
		"first_name": "Test",
		"last_name":  "User",
	}

	jsonData, _ := json.Marshal(createUserPayload)
	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/register", bytes.NewBuffer(jsonData))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+adminToken)

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

	// Check if user creation succeeded or already exists (409 Conflict)
	if resp.StatusCode == http.StatusConflict {
		// User already exists, try to login to get their access token
		t.Logf("User maxdevices@example.com already exists, logging in to get ID")
		loginPayload := map[string]interface{}{
			"login":    "maxdevices@example.com",
			"password": "Test@Pass123!",
		}
		loginData, _ := json.Marshal(loginPayload)
		loginReq, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(loginData))
		loginReq.Header.Set("Content-Type", "application/json")

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

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

		// Extract user_id from JWT or response
		// The access_token contains the user ID in the JWT claims
		if accessToken, ok := loginResult["access_token"].(string); ok {
			// Simple JWT parsing to get user_id
			// JWT format: header.payload.signature
			parts := strings.Split(accessToken, ".")
			if len(parts) >= 2 {
				// Decode payload (base64url)
				payload := parts[1]
				// Add padding if needed
				for len(payload)%4 != 0 {
					payload += "="
				}
				// Decode base64
				decodedBytes, err := base64.StdEncoding.DecodeString(payload)
				if err == nil {
					var claims map[string]interface{}
					if err := json.Unmarshal(decodedBytes, &claims); err == nil {
						t.Logf("JWT claims: %+v", claims)
						if userIDStr, ok := claims["user_id"].(string); ok {
							userID, _ := uuid.Parse(userIDStr)
							t.Logf("Extracted userID from JWT: %s", userID)
							return userID
						} else {
							t.Logf("user_id not found in JWT claims")
						}
					} else {
						t.Logf("Failed to unmarshal JWT claims: %v", err)
					}
				} else {
					t.Logf("Failed to decode base64: %v", err)
				}
			} else {
				t.Logf("JWT doesn't have enough parts: %d", len(parts))
			}
		} else {
			t.Logf("access_token not found in login result")
		}

		// If JWT parsing fails, return empty UUID
		return uuid.UUID{}
	}

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

	// Check if user creation was successful
	if result["user"] == nil {
		// User creation failed for another reason
		t.Logf("User creation failed, response: %+v", result)
		return uuid.UUID{}
	}

	userIDStr := result["user"].(map[string]interface{})["id"].(string)
	userID, _ := uuid.Parse(userIDStr)

	return userID
}

// Helper function to get admin token
func getAdminToken(t *testing.T, ts *httptest.Server, userID uuid.UUID) string {
	// First, try to login as the admin user (testuser who was created with admin role)
	loginPayload := map[string]interface{}{
		"login":    "testuser@tests.bookhoard.internal",
		"password": "Test@Pass123!",
	}

	jsonData, _ := json.Marshal(loginPayload)
	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(jsonData))
	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)

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

	// Safe type assertion with check
	if accessToken, ok := result["access_token"].(string); ok {
		return accessToken
	}

	// Handle error case - if login failed, return empty string
	return ""
}

// Helper function to login user by credentials
func loginTestUserByCredentials(t *testing.T, ts *httptest.Server, email, password string) string {
	loginPayload := map[string]interface{}{
		"login":    email,
		"password": password,
	}

	jsonData, _ := json.Marshal(loginPayload)
	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(jsonData))
	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)

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

	// Safe type assertion with check
	if accessToken, ok := result["access_token"].(string); ok {
		return accessToken
	}

	// Login failed - return empty string
	return ""
}
