package workspaces_controllers

import (
	"fmt"
	"net/http"
	"testing"

	audit_logs "postgresus-backend/internal/features/audit_logs"
	users_dto "postgresus-backend/internal/features/users/dto"
	users_enums "postgresus-backend/internal/features/users/enums"
	users_models "postgresus-backend/internal/features/users/models"
	users_services "postgresus-backend/internal/features/users/services"
	users_testing "postgresus-backend/internal/features/users/testing"
	workspaces_dto "postgresus-backend/internal/features/workspaces/dto"
	workspaces_models "postgresus-backend/internal/features/workspaces/models"
	workspaces_testing "postgresus-backend/internal/features/workspaces/testing"
	test_utils "postgresus-backend/internal/util/testing"

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

func Test_CreateWorkspace_PermissionsEnforced(t *testing.T) {
	tests := []struct {
		name                  string
		userRole              users_enums.UserRole
		memberCreationEnabled bool
		expectSuccess         bool
		expectedStatusCode    int
		expectedWorkspaceName string
	}{
		{
			name:                  "member can create workspace when enabled",
			userRole:              users_enums.UserRoleMember,
			memberCreationEnabled: true,
			expectSuccess:         true,
			expectedStatusCode:    http.StatusOK,
			expectedWorkspaceName: "Test Workspace",
		},
		{
			name:                  "member cannot create workspace when disabled",
			userRole:              users_enums.UserRoleMember,
			memberCreationEnabled: false,
			expectSuccess:         false,
			expectedStatusCode:    http.StatusForbidden,
		},
		{
			name:                  "admin can create workspace when member creation disabled",
			userRole:              users_enums.UserRoleAdmin,
			memberCreationEnabled: false,
			expectSuccess:         true,
			expectedStatusCode:    http.StatusOK,
			expectedWorkspaceName: "Admin Workspace",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := workspaces_testing.CreateTestRouter(
				GetWorkspaceController(),
				GetMembershipController(),
			)

			if tt.memberCreationEnabled {
				users_testing.EnableMemberWorkspaceCreation()
			} else {
				users_testing.DisableMemberWorkspaceCreation()
			}
			defer users_testing.ResetSettingsToDefaults()

			user := users_testing.CreateTestUser(tt.userRole)

			uniqueID := uuid.New().String()[:8]
			workspaceName := tt.expectedWorkspaceName + " " + uniqueID
			request := workspaces_dto.CreateWorkspaceRequestDTO{
				Name: workspaceName,
			}

			if tt.expectSuccess {
				var response workspaces_dto.WorkspaceResponseDTO
				test_utils.MakePostRequestAndUnmarshal(
					t,
					router,
					"/api/v1/workspaces",
					"Bearer "+user.Token,
					request,
					tt.expectedStatusCode,
					&response,
				)

				assert.Equal(t, workspaceName, response.Name)
				assert.NotEqual(t, uuid.Nil, response.ID)
				assert.Equal(t, users_enums.WorkspaceRoleOwner, *response.UserRole)
			} else {
				resp := test_utils.MakePostRequest(
					t,
					router,
					"/api/v1/workspaces",
					"Bearer "+user.Token,
					request,
					tt.expectedStatusCode,
				)
				assert.Contains(t, string(resp.Body), "insufficient permissions to create workspaces")
			}
		})
	}
}

func Test_CreateWorkspace_WithInvalidJSON_ReturnsBadRequest(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	user := users_testing.CreateTestUser(users_enums.UserRoleAdmin)

	resp := test_utils.MakeRequest(t, router, test_utils.RequestOptions{
		Method:         "POST",
		URL:            "/api/v1/workspaces",
		Body:           "invalid json",
		AuthToken:      "Bearer " + user.Token,
		ExpectedStatus: http.StatusBadRequest,
	})

	assert.Contains(t, string(resp.Body), "Invalid request format")
}

func Test_CreateWorkspace_WithoutAuthToken_ReturnsUnauthorized(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)

	request := workspaces_dto.CreateWorkspaceRequestDTO{
		Name: "Test Workspace",
	}

	test_utils.MakePostRequest(
		t,
		router,
		"/api/v1/workspaces",
		"",
		request,
		http.StatusUnauthorized,
	)
}

func Test_GetUserWorkspaces_WhenUserHasWorkspaces_ReturnsWorkspacesList(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	user := users_testing.CreateTestUser(users_enums.UserRoleMember)

	workspace1, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		"Workspace 1",
		user.Token,
		router,
	)
	workspace2, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		"Workspace 2",
		user.Token,
		router,
	)

	var response workspaces_dto.ListWorkspacesResponseDTO
	test_utils.MakeGetRequestAndUnmarshal(
		t,
		router,
		"/api/v1/workspaces",
		"Bearer "+user.Token,
		http.StatusOK,
		&response,
	)

	assert.GreaterOrEqual(t, len(response.Workspaces), 2)

	workspaceNames := make([]string, len(response.Workspaces))
	for i, w := range response.Workspaces {
		workspaceNames[i] = w.Name
	}
	assert.Contains(t, workspaceNames, workspace1.Name)
	assert.Contains(t, workspaceNames, workspace2.Name)
}

func Test_GetUserWorkspaces_WithoutAuthToken_ReturnsUnauthorized(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	test_utils.MakeGetRequest(t, router, "/api/v1/workspaces", "", http.StatusUnauthorized)
}

func Test_GetSingleWorkspace_PermissionsEnforced(t *testing.T) {
	tests := []struct {
		name               string
		workspaceRole      *users_enums.WorkspaceRole
		isGlobalAdmin      bool
		expectSuccess      bool
		expectedStatusCode int
	}{
		{
			name:               "workspace owner can get workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace admin can get workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace member can get workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace viewer can get workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "non-member cannot get workspace",
			workspaceRole:      nil,
			isGlobalAdmin:      false,
			expectSuccess:      false,
			expectedStatusCode: http.StatusForbidden,
		},
		{
			name:               "global admin can get workspace",
			workspaceRole:      nil,
			isGlobalAdmin:      true,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := workspaces_testing.CreateTestRouter(
				GetWorkspaceController(),
				GetMembershipController(),
			)
			owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
			workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
				"Test Workspace",
				owner.Token,
				router,
			)

			var testUserToken string
			if tt.isGlobalAdmin {
				admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
				testUserToken = admin.Token
			} else if tt.workspaceRole != nil && *tt.workspaceRole == users_enums.WorkspaceRoleOwner {
				testUserToken = owner.Token
			} else if tt.workspaceRole != nil {
				member := users_testing.CreateTestUser(users_enums.UserRoleMember)
				workspaces_testing.AddMemberToWorkspace(workspace, member, *tt.workspaceRole, owner.Token, router)
				testUserToken = member.Token
			} else {
				nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
				testUserToken = nonMember.Token
			}

			if tt.expectSuccess {
				var response workspaces_models.Workspace
				test_utils.MakeGetRequestAndUnmarshal(
					t,
					router,
					"/api/v1/workspaces/"+workspace.ID.String(),
					"Bearer "+testUserToken,
					tt.expectedStatusCode,
					&response,
				)

				assert.Equal(t, workspace.ID, response.ID)
				assert.Equal(t, "Test Workspace", response.Name)
			} else {
				resp := test_utils.MakeGetRequest(
					t,
					router,
					"/api/v1/workspaces/"+workspace.ID.String(),
					"Bearer "+testUserToken,
					tt.expectedStatusCode,
				)
				assert.Contains(t, string(resp.Body), "insufficient permissions to view workspace")
			}
		})
	}
}

func Test_GetSingleWorkspace_WithInvalidWorkspaceID_ReturnsBadRequest(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	user := users_testing.CreateTestUser(users_enums.UserRoleMember)

	resp := test_utils.MakeGetRequest(
		t,
		router,
		"/api/v1/workspaces/invalid-uuid",
		"Bearer "+user.Token,
		http.StatusBadRequest,
	)
	assert.Contains(t, string(resp.Body), "Invalid workspace ID")
}

func Test_UpdateWorkspace_PermissionsEnforced(t *testing.T) {
	tests := []struct {
		name               string
		workspaceRole      users_enums.WorkspaceRole
		expectSuccess      bool
		expectedStatusCode int
	}{
		{
			name:               "workspace owner can update workspace",
			workspaceRole:      users_enums.WorkspaceRoleOwner,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace admin can update workspace",
			workspaceRole:      users_enums.WorkspaceRoleAdmin,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace member cannot update workspace",
			workspaceRole:      users_enums.WorkspaceRoleMember,
			expectSuccess:      false,
			expectedStatusCode: http.StatusForbidden,
		},
		{
			name:               "workspace viewer cannot update workspace",
			workspaceRole:      users_enums.WorkspaceRoleViewer,
			expectSuccess:      false,
			expectedStatusCode: http.StatusForbidden,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := workspaces_testing.CreateTestRouter(
				GetWorkspaceController(),
				GetMembershipController(),
			)
			owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
			workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
				"Original Name",
				owner.Token,
				router,
			)

			var testUserToken string
			if tt.workspaceRole == users_enums.WorkspaceRoleOwner {
				testUserToken = owner.Token
			} else {
				member := users_testing.CreateTestUser(users_enums.UserRoleMember)
				workspaces_testing.AddMemberToWorkspace(workspace, member, tt.workspaceRole, owner.Token, router)
				testUserToken = member.Token
			}

			updateRequest := workspaces_models.Workspace{
				Name: "Updated Name",
			}

			if tt.expectSuccess {
				var response workspaces_models.Workspace
				test_utils.MakePutRequestAndUnmarshal(
					t,
					router,
					"/api/v1/workspaces/"+workspace.ID.String(),
					"Bearer "+testUserToken,
					updateRequest,
					tt.expectedStatusCode,
					&response,
				)

				assert.Equal(t, workspace.ID, response.ID)
				assert.Equal(t, "Updated Name", response.Name)
			} else {
				resp := test_utils.MakePutRequest(
					t,
					router,
					"/api/v1/workspaces/"+workspace.ID.String(),
					"Bearer "+testUserToken,
					updateRequest,
					tt.expectedStatusCode,
				)
				assert.Contains(t, string(resp.Body), "insufficient permissions to update workspace")
			}
		})
	}
}

func Test_DeleteWorkspace_PermissionsEnforced(t *testing.T) {
	tests := []struct {
		name               string
		workspaceRole      *users_enums.WorkspaceRole
		isGlobalAdmin      bool
		expectSuccess      bool
		expectedStatusCode int
	}{
		{
			name:               "workspace owner can delete workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "global admin can delete workspace",
			workspaceRole:      nil,
			isGlobalAdmin:      true,
			expectSuccess:      true,
			expectedStatusCode: http.StatusOK,
		},
		{
			name:               "workspace member cannot delete workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      false,
			expectedStatusCode: http.StatusForbidden,
		},
		{
			name:               "workspace admin cannot delete workspace",
			workspaceRole:      func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
			isGlobalAdmin:      false,
			expectSuccess:      false,
			expectedStatusCode: http.StatusForbidden,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := workspaces_testing.CreateTestRouter(
				GetWorkspaceController(),
				GetMembershipController(),
			)
			owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
			workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
				"Test Workspace",
				owner.Token,
				router,
			)

			var testUserToken string
			if tt.isGlobalAdmin {
				admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
				testUserToken = admin.Token
			} else if tt.workspaceRole != nil && *tt.workspaceRole == users_enums.WorkspaceRoleOwner {
				testUserToken = owner.Token
			} else if tt.workspaceRole != nil {
				member := users_testing.CreateTestUser(users_enums.UserRoleMember)
				workspaces_testing.AddMemberToWorkspace(workspace, member, *tt.workspaceRole, owner.Token, router)
				testUserToken = member.Token
			}

			resp := test_utils.MakeRequest(t, router, test_utils.RequestOptions{
				Method:         "DELETE",
				URL:            "/api/v1/workspaces/" + workspace.ID.String(),
				AuthToken:      "Bearer " + testUserToken,
				ExpectedStatus: tt.expectedStatusCode,
			})

			if tt.expectSuccess {
				assert.Contains(t, string(resp.Body), "Workspace deleted successfully")
			} else {
				assert.Contains(t, string(resp.Body), "only workspace owner or admin can delete workspace")
			}
		})
	}
}

func Test_GetWorkspaceAuditLogs_WhenUserIsWorkspaceAdmin_ReturnsAuditLogs(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
	workspaceAdmin := users_testing.CreateTestUser(users_enums.UserRoleMember)

	uniqueID := uuid.New()
	workspaceName := fmt.Sprintf("WorkspaceAdmin Test %s", uniqueID.String()[:8])
	workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		workspaceName,
		owner.Token,
		router,
	)

	workspaces_testing.AddMemberToWorkspace(
		workspace,
		workspaceAdmin,
		users_enums.WorkspaceRoleMember,
		owner.Token,
		router,
	)
	var response audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"Bearer "+workspaceAdmin.Token, http.StatusOK, &response)

	assert.GreaterOrEqual(t, len(response.AuditLogs), 2) // Create + Add member
	for _, log := range response.AuditLogs {
		assert.Equal(t, &workspace.ID, log.WorkspaceID)
	}
}

func Test_GetWorkspaceAuditLogs_WithMultipleWorkspaces_ReturnsOnlyWorkspaceSpecificLogs(
	t *testing.T,
) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	owner1 := users_testing.CreateTestUser(users_enums.UserRoleMember)
	owner2 := users_testing.CreateTestUser(users_enums.UserRoleMember)

	uniqueID1 := uuid.New()
	uniqueID2 := uuid.New()
	workspaceName1 := fmt.Sprintf("Workspace Test %s", uniqueID1.String()[:8])
	workspaceName2 := fmt.Sprintf("Workspace Test %s", uniqueID2.String()[:8])

	workspace1, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		workspaceName1,
		owner1.Token,
		router,
	)
	workspace2, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		workspaceName2,
		owner2.Token,
		router,
	)

	updateWorkspace1 := workspaces_models.Workspace{
		Name: "Updated " + workspace1.Name,
	}
	test_utils.MakePutRequest(
		t,
		router,
		"/api/v1/workspaces/"+workspace1.ID.String(),
		"Bearer "+owner1.Token,
		updateWorkspace1,
		http.StatusOK,
	)

	updateWorkspace2 := workspaces_models.Workspace{
		Name: "Updated " + workspace2.Name,
	}
	test_utils.MakePutRequest(
		t,
		router,
		"/api/v1/workspaces/"+workspace2.ID.String(),
		"Bearer "+owner2.Token,
		updateWorkspace2,
		http.StatusOK,
	)

	var workspace1Response audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace1.ID.String()+"/audit-logs?limit=50",
		"Bearer "+owner1.Token, http.StatusOK, &workspace1Response)

	var workspace2Response audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace2.ID.String()+"/audit-logs?limit=50",
		"Bearer "+owner2.Token, http.StatusOK, &workspace2Response)

	assert.GreaterOrEqual(t, len(workspace1Response.AuditLogs), 2)
	for _, log := range workspace1Response.AuditLogs {
		assert.Equal(t, &workspace1.ID, log.WorkspaceID)
		assert.Contains(t, log.Message, workspaceName1)
	}

	assert.GreaterOrEqual(t, len(workspace2Response.AuditLogs), 2)
	for _, log := range workspace2Response.AuditLogs {
		assert.Equal(t, &workspace2.ID, log.WorkspaceID)
		assert.Contains(t, log.Message, workspaceName2)
	}

	workspace1Messages := extractAuditLogMessages(workspace1Response.AuditLogs)
	workspace2Messages := extractAuditLogMessages(workspace2Response.AuditLogs)

	for _, msg := range workspace1Messages {
		assert.NotContains(
			t,
			msg,
			workspaceName2,
			"Workspace1 logs should not contain Workspace2 name",
		)
	}

	for _, msg := range workspace2Messages {
		assert.NotContains(
			t,
			msg,
			workspaceName1,
			"Workspace2 logs should not contain Workspace1 name",
		)
	}
}

func Test_GetWorkspaceAuditLogs_WithDifferentUserRoles_EnforcesPermissionsCorrectly(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	globalAdmin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
	owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
	member := users_testing.CreateTestUser(users_enums.UserRoleMember)
	nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)

	uniqueID := uuid.New()
	workspaceName := fmt.Sprintf("Audit Test Workspace %s", uniqueID.String()[:8])
	workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		workspaceName,
		owner.Token,
		router,
	)

	workspaces_testing.AddMemberToWorkspace(
		workspace,
		member,
		users_enums.WorkspaceRoleViewer,
		owner.Token,
		router,
	)
	var ownerResponse audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"Bearer "+owner.Token, http.StatusOK, &ownerResponse)

	assert.GreaterOrEqual(t, len(ownerResponse.AuditLogs), 2)
	var memberResponse audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"Bearer "+member.Token, http.StatusOK, &memberResponse)

	assert.GreaterOrEqual(t, len(memberResponse.AuditLogs), 2)

	var globalAdminResponse audit_logs.GetAuditLogsResponse
	test_utils.MakeGetRequestAndUnmarshal(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"Bearer "+globalAdmin.Token, http.StatusOK, &globalAdminResponse)

	assert.GreaterOrEqual(t, len(globalAdminResponse.AuditLogs), 2)

	resp := test_utils.MakeGetRequest(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"Bearer "+nonMember.Token, http.StatusForbidden)

	assert.Contains(t, string(resp.Body), "insufficient permissions to view workspace audit logs")
}

func Test_GetWorkspaceAuditLogs_WithoutAuthToken_ReturnsUnauthorized(t *testing.T) {
	router := workspaces_testing.CreateTestRouter(
		GetWorkspaceController(),
		GetMembershipController(),
	)
	owner := users_testing.CreateTestUser(users_enums.UserRoleMember)

	workspace, _ := workspaces_testing.CreateTestWorkspaceWithToken(
		"Test Workspace",
		owner.Token,
		router,
	)

	test_utils.MakeGetRequest(t, router,
		"/api/v1/workspaces/"+workspace.ID.String()+"/audit-logs",
		"", http.StatusUnauthorized)
}

func extractAuditLogMessages(logs []*audit_logs.AuditLogDTO) []string {
	messages := make([]string, len(logs))
	for i, log := range logs {
		messages[i] = log.Message
	}
	return messages
}

func getUserFromSignInResponse(response *users_dto.SignInResponseDTO) *users_models.User {
	userService := users_services.GetUserService()
	user, err := userService.GetUserByID(response.UserID)
	if err != nil {
		panic(err)
	}
	return user
}

type AuditLogWriterStub struct{}

func (a *AuditLogWriterStub) WriteAuditLog(
	message string,
	userID *uuid.UUID,
	workspaceID *uuid.UUID,
) {
}
