package rest_test

import (
	"encoding/json"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/stretchr/testify/assert"
)

func init() {
	gin.SetMode(gin.TestMode)
}

// TestNormalizeType tests the type normalization helper
func TestNormalizeType(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"lowercase dashboard", "dashboard", "Dashboard"},
		{"uppercase DASHBOARD", "DASHBOARD", "Dashboard"},
		{"mixed case Dashboard", "Dashboard", "Dashboard"},
		{"lowercase report", "report", "Report"},
		{"uppercase REPORT", "REPORT", "Report"},
		{"invalid type defaults to Report", "invalid", "Report"},
		{"empty string defaults to Report", "", "Report"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportNormalizeType(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestNormalizeAccessType tests the access type normalization helper
func TestNormalizeAccessType(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"lowercase hidden", "hidden", "Hidden"},
		{"uppercase HIDDEN", "HIDDEN", "Hidden"},
		{"lowercase public", "public", "Public"},
		{"uppercase PUBLIC", "PUBLIC", "Public"},
		{"lowercase shared", "shared", "Shared"},
		{"uppercase SHARED", "SHARED", "Shared"},
		{"invalid defaults to Hidden", "invalid", "Hidden"},
		{"empty string defaults to Hidden", "", "Hidden"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportNormalizeAccessType(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestNormalizeSharedToType tests the shared to type normalization helper
func TestNormalizeSharedToType(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"lowercase user", "user", "User"},
		{"uppercase USER", "USER", "User"},
		{"lowercase group", "group", "Group"},
		{"uppercase GROUP", "GROUP", "Group"},
		{"lowercase role", "role", "Role"},
		{"uppercase ROLE", "ROLE", "Role"},
		{"invalid defaults to User", "invalid", "User"},
		{"empty string defaults to User", "", "User"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportNormalizeSharedToType(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestNormalizeAccessLevel tests the access level normalization helper
func TestNormalizeAccessLevel(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"lowercase view", "view", "View"},
		{"uppercase VIEW", "VIEW", "View"},
		{"lowercase manage", "manage", "Manage"},
		{"uppercase MANAGE", "MANAGE", "Manage"},
		{"invalid defaults to View", "invalid", "View"},
		{"empty string defaults to View", "", "View"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportNormalizeAccessLevel(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestValidateID tests the ID validation helper
func TestValidateID(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{"valid UUID", "550e8400-e29b-41d4-a716-446655440000", true},
		{"valid UUID uppercase", "550E8400-E29B-41D4-A716-446655440000", true},
		{"invalid format with spaces", "invalid id!", false},
		{"empty string", "", false},
		{"alphanumeric with hyphen", "invalid-id", true},       // hyphens allowed
		{"alphanumeric with underscore", "AGENT_READONLY_001", true}, // underscores allowed
		{"salesforce 15 char", "001000000000001", true},    // Salesforce-style 15-char IDs accepted (imported data)
		{"salesforce 18 char", "001000000000001AAA", true}, // Salesforce-style 18-char IDs accepted
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportValidateID(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestFolderResponse_JSONMarshaling tests that FolderResponse serializes correctly
func TestFolderResponse_JSONMarshaling(t *testing.T) {
	response := rest.FolderResponse{
		ID:               "550e8400-e29b-41d4-a716-446655440000",
		Name:             "Test Folder",
		Label:            "Test Folder Label",
		DeveloperName:    "Test_Folder",
		Description:      "A test folder",
		Type:             "Dashboard",
		AccessType:       "Shared",
		ParentFolderID:   "550e8400-e29b-41d4-a716-446655440001",
		CreatedDate:      "2024-01-01T00:00:00Z",
		LastModifiedDate: "2024-01-02T00:00:00Z",
	}

	data, err := json.Marshal(response)
	assert.NoError(t, err)

	var parsed map[string]interface{}
	err = json.Unmarshal(data, &parsed)
	assert.NoError(t, err)

	assert.Equal(t, response.ID, parsed["id"])
	assert.Equal(t, response.Name, parsed["name"])
	assert.Equal(t, response.Type, parsed["type"])
	assert.Equal(t, response.AccessType, parsed["accessType"])
	assert.Equal(t, response.ParentFolderID, parsed["parentFolderId"])
}

// TestFolderShareResponse_JSONMarshaling tests that FolderShareResponse serializes correctly
func TestFolderShareResponse_JSONMarshaling(t *testing.T) {
	response := rest.FolderShareResponse{
		ID:           "550e8400-e29b-41d4-a716-446655440000",
		SharedToID:   "550e8400-e29b-41d4-a716-446655440001",
		SharedToType: "User",
		AccessLevel:  "View",
	}

	data, err := json.Marshal(response)
	assert.NoError(t, err)

	var parsed map[string]interface{}
	err = json.Unmarshal(data, &parsed)
	assert.NoError(t, err)

	assert.Equal(t, response.ID, parsed["id"])
	assert.Equal(t, response.SharedToID, parsed["sharedToId"])
	assert.Equal(t, response.SharedToType, parsed["sharedToType"])
	assert.Equal(t, response.AccessLevel, parsed["accessLevel"])
}

// TestCreateFolderRequest_JSONBinding tests CreateFolderRequest binding
func TestCreateFolderRequest_JSONBinding(t *testing.T) {
	t.Run("valid request", func(t *testing.T) {
		body := `{"name": "Sales Reports", "type": "Report", "accessType": "Shared"}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("POST", "/folders", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.CreateFolderRequest
		err := c.ShouldBindJSON(&req)
		assert.NoError(t, err)
		assert.Equal(t, "Sales Reports", req.Name)
		assert.Equal(t, "Report", req.Type)
		assert.Equal(t, "Shared", req.AccessType)
	})

	t.Run("missing required name", func(t *testing.T) {
		body := `{"type": "Report"}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("POST", "/folders", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.CreateFolderRequest
		err := c.ShouldBindJSON(&req)
		assert.Error(t, err) // name is required
	})
}

// TestUpdateFolderRequest_JSONBinding tests UpdateFolderRequest binding
func TestUpdateFolderRequest_JSONBinding(t *testing.T) {
	t.Run("partial update", func(t *testing.T) {
		body := `{"name": "Updated Name"}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PATCH", "/folders/123", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.UpdateFolderRequest
		err := c.ShouldBindJSON(&req)
		assert.NoError(t, err)
		assert.NotNil(t, req.Name)
		assert.Equal(t, "Updated Name", *req.Name)
		assert.Nil(t, req.Description) // Not provided
	})
}

// TestAddFolderSharesRequest_JSONBinding tests AddFolderSharesRequest binding
func TestAddFolderSharesRequest_JSONBinding(t *testing.T) {
	t.Run("valid shares request", func(t *testing.T) {
		body := `{"shares": [{"sharedToId": "550e8400-e29b-41d4-a716-446655440000", "sharedToType": "User", "accessLevel": "View"}]}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("POST", "/folders/123/shares", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.AddFolderSharesRequest
		err := c.ShouldBindJSON(&req)
		assert.NoError(t, err)
		assert.Len(t, req.Shares, 1)
		assert.Equal(t, "550e8400-e29b-41d4-a716-446655440000", req.Shares[0].SharedToID)
	})

	t.Run("missing required shares", func(t *testing.T) {
		body := `{}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("POST", "/folders/123/shares", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.AddFolderSharesRequest
		err := c.ShouldBindJSON(&req)
		assert.Error(t, err) // shares is required
	})
}

// TestPaginationParams tests pagination parameter parsing
func TestPaginationParams(t *testing.T) {
	tests := []struct {
		name             string
		url              string
		expectedPage     int
		expectedPageSize int
	}{
		{"default values", "/folders", 0, 200},
		{"custom page", "/folders?page=5", 5, 200},
		{"custom pageSize", "/folders?pageSize=50", 0, 50},
		{"both params", "/folders?page=2&pageSize=100", 2, 100},
		{"pageSize capped at 2000", "/folders?pageSize=5000", 0, 2000},
		{"negative page ignored", "/folders?page=-1", 0, 200},
		{"zero pageSize ignored", "/folders?pageSize=0", 0, 200},
		{"invalid page ignored", "/folders?page=abc", 0, 200},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)
			c.Request = httptest.NewRequest("GET", tt.url, nil)

			page, pageSize := rest.ExportParsePaginationParams(c)
			assert.Equal(t, tt.expectedPage, page, "page mismatch")
			assert.Equal(t, tt.expectedPageSize, pageSize, "pageSize mismatch")
		})
	}
}

// TestFolderHandler_RegisterRoutes tests that all routes are registered
func TestFolderHandler_RegisterRoutes(t *testing.T) {
	// This test verifies the route registration without needing a full service manager
	// We check that the handler can be created (basic sanity test)
	handler := rest.NewFolderHandler(nil)
	assert.NotNil(t, handler)
}

// TestHTTPStatusCodes tests that handlers return correct HTTP status codes
func TestHTTPStatusCodes(t *testing.T) {
	t.Run("Unauthorized panics without user context", func(t *testing.T) {
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("GET", "/folders", nil)
		// No user in context

		handler := rest.NewFolderHandler(nil)
		assert.Panics(t, func() { handler.ListFolders(c) })
	})
}

// TestShareBelongsToFolder tests the share ownership helper
func TestShareBelongsToFolder(t *testing.T) {
	tests := []struct {
		name     string
		share    map[string]interface{}
		folderID string
		expected bool
	}{
		{
			name:     "share belongs to folder",
			share:    map[string]interface{}{"FolderId": "folder-123", "Id": "share-1"},
			folderID: "folder-123",
			expected: true,
		},
		{
			name:     "share belongs to different folder",
			share:    map[string]interface{}{"FolderId": "folder-456", "Id": "share-1"},
			folderID: "folder-123",
			expected: false,
		},
		{
			name:     "share has no FolderId",
			share:    map[string]interface{}{"Id": "share-1"},
			folderID: "folder-123",
			expected: false,
		},
		{
			name:     "FolderId is not a string",
			share:    map[string]interface{}{"FolderId": 123, "Id": "share-1"},
			folderID: "folder-123",
			expected: false,
		},
		{
			name:     "empty share",
			share:    map[string]interface{}{},
			folderID: "folder-123",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := rest.ExportShareBelongsToFolder(tt.share, tt.folderID)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestUpdateFolderShareRequest_JSONBinding tests UpdateFolderShareRequest binding
func TestUpdateFolderShareRequest_JSONBinding(t *testing.T) {
	t.Run("valid request", func(t *testing.T) {
		body := `{"accessLevel": "Manage"}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PATCH", "/folders/123/shares/456", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.UpdateFolderShareRequest
		err := c.ShouldBindJSON(&req)
		assert.NoError(t, err)
		assert.Equal(t, "Manage", req.AccessLevel)
	})

	t.Run("missing required accessLevel", func(t *testing.T) {
		body := `{}`
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = httptest.NewRequest("PATCH", "/folders/123/shares/456", strings.NewReader(body))
		c.Request.Header.Set("Content-Type", "application/json")

		var req rest.UpdateFolderShareRequest
		err := c.ShouldBindJSON(&req)
		assert.Error(t, err) // accessLevel is required
	})
}
