package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/go-chi/chi/v5"
	"gorm.io/gorm"
	"goyco/internal/database"
	"goyco/internal/middleware"
	"goyco/internal/security"
	"goyco/internal/testutils"
	"goyco/internal/validation"
)

func TestPostHandler_XSSProtection_Comprehensive(t *testing.T) {
	maliciousInputs := testutils.GetMaliciousInputs()

	for _, payload := range maliciousInputs.XSSPayloads {
		t.Run("XSS_"+payload[:minLen(20, len(payload))], func(t *testing.T) {
			repo := &testutils.PostRepositoryStub{
				CreateFn: func(post *database.Post) error {
					return nil
				},
			}

			handler := NewPostHandler(repo, nil, nil)

			postData := map[string]string{
				"title":   payload,
				"url":     "https://example.com",
				"content": "Test content",
			}

			body, _ := json.Marshal(postData)
			request := createCreatePostRequest(string(body))
			request.Header.Set("Content-Type", "application/json")
			request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
			recorder := httptest.NewRecorder()

			handler.CreatePost(recorder, request)

			if recorder.Code != http.StatusCreated {
				t.Errorf("Expected status %d, got %d. Body: %s", http.StatusCreated, recorder.Code, recorder.Body.String())
				return
			}

			var response CommonResponse
			if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
				t.Fatalf("Failed to decode response: %v", err)
			}

			if !response.Success {
				t.Errorf("Expected successful response, got error: %s", response.Error)
				return
			}

			dataMap, ok := response.Data.(map[string]any)
			if !ok {
				t.Fatalf("Expected data to be a map, got %T", response.Data)
			}

			title, ok := dataMap["title"].(string)
			if !ok {
				t.Fatalf("Expected title to be a string, got %T", dataMap["title"])
			}

			expectedSanitized := security.SanitizeInput(payload)
			if title != expectedSanitized {
				t.Errorf("Expected sanitized title %q, got %q", expectedSanitized, title)
			}

			if title == payload {
				t.Errorf("Title was not sanitized - original payload %q matches response %q", payload, title)
			}
		})
	}
}

func minLen(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func TestPostHandler_InputValidation(t *testing.T) {
	tests := []struct {
		name           string
		title          string
		content        string
		url            string
		expectedStatus int
		description    string
	}{
		{
			name:           "title too long",
			title:          string(make([]byte, 201)),
			content:        "Normal content",
			url:            "https://example.com",
			expectedStatus: http.StatusBadRequest,
			description:    "Title should be limited to 200 characters",
		},
		{
			name:           "content too long",
			title:          "Normal title",
			content:        string(make([]byte, 10001)),
			url:            "https://example.com",
			expectedStatus: http.StatusBadRequest,
			description:    "Content should be limited to 10,000 characters",
		},
		{
			name:           "invalid URL protocol",
			title:          "Normal title",
			content:        "Normal content",
			url:            "ftp://example.com",
			expectedStatus: http.StatusBadRequest,
			description:    "Only HTTP and HTTPS URLs should be allowed",
		},
		{
			name:           "localhost URL blocked",
			title:          "Normal title",
			content:        "Normal content",
			url:            "http://localhost:8080",
			expectedStatus: http.StatusBadRequest,
			description:    "Localhost URLs should be blocked",
		},
		{
			name:           "private IP URL blocked",
			title:          "Normal title",
			content:        "Normal content",
			url:            "http://192.168.1.1",
			expectedStatus: http.StatusBadRequest,
			description:    "Private IP URLs should be blocked",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &testutils.PostRepositoryStub{}
			handler := NewPostHandler(repo, nil, nil)

			postData := map[string]string{
				"title":   tt.title,
				"url":     tt.url,
				"content": tt.content,
			}

			body, _ := json.Marshal(postData)
			request := createCreatePostRequest(string(body))
			request.Header.Set("Content-Type", "application/json")
			request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))
			recorder := httptest.NewRecorder()

			handler.CreatePost(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)
		})
	}
}

func TestAuthHandler_PasswordValidation(t *testing.T) {
	tests := []struct {
		name           string
		password       string
		expectedStatus int
		description    string
	}{
		{
			name:           "weak password",
			password:       "123",
			expectedStatus: http.StatusBadRequest,
			description:    "Weak passwords should be rejected",
		},
		{
			name:           "password without letters",
			password:       "12345678",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without letters should be rejected",
		},
		{
			name:           "password without numbers",
			password:       "password",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without numbers should be rejected",
		},
		{
			name:           "password without special chars",
			password:       "Password123",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords without special characters should be rejected",
		},
		{
			name:           "password too short",
			password:       "Pass1!",
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords shorter than 8 characters should be rejected",
		},
		{
			name:           "password too long",
			password:       string(make([]byte, 129)),
			expectedStatus: http.StatusBadRequest,
			description:    "Passwords that are too long should be rejected",
		},
		{
			name:           "empty password",
			password:       "",
			expectedStatus: http.StatusBadRequest,
			description:    "Empty passwords should be rejected",
		},
		{
			name:           "valid password",
			password:       "Password123!",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords should be accepted",
		},
		{
			name:           "valid password with underscore",
			password:       "Password123_",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with underscore should be accepted",
		},
		{
			name:           "valid password with hyphen",
			password:       "Password123-",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with hyphen should be accepted",
		},
		{
			name:           "valid password with unicode",
			password:       "Pássw0rd123!",
			expectedStatus: http.StatusCreated,
			description:    "Valid passwords with unicode should be accepted",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &testutils.UserRepositoryStub{
				GetByUsernameFn: func(string) (*database.User, error) {
					return nil, gorm.ErrRecordNotFound
				},
				CreateFn: func(user *database.User) error {
					return nil
				},
			}

			handler := newAuthHandler(repo)

			registerData := map[string]string{
				"username": "testuser",
				"email":    "test@example.com",
				"password": tt.password,
			}

			body, _ := json.Marshal(registerData)
			request := createRegisterRequest(string(body))
			request.Header.Set("Content-Type", "application/json")
			recorder := httptest.NewRecorder()

			handler.Register(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)
		})
	}
}

func TestAuthHandler_UsernameSanitization(t *testing.T) {
	tests := []struct {
		name           string
		username       string
		expectedStatus int
		description    string
	}{
		{
			name:           "username with special chars",
			username:       "test@user#123",
			expectedStatus: http.StatusCreated,
			description:    "Special characters should be removed from username",
		},
		{
			name:           "username with script tags",
			username:       "test<script>alert('xss')</script>user",
			expectedStatus: http.StatusCreated,
			description:    "Script tags should be removed from username",
		},
		{
			name:           "username starting with special char",
			username:       "@testuser",
			expectedStatus: http.StatusCreated,
			description:    "Username starting with special char should be prefixed",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var capturedUsername string
			repo := &testutils.UserRepositoryStub{
				GetByUsernameFn: func(username string) (*database.User, error) {
					capturedUsername = username
					return nil, gorm.ErrRecordNotFound
				},
				CreateFn: func(user *database.User) error {
					return nil
				},
			}

			handler := newAuthHandler(repo)

			registerData := map[string]string{
				"username": tt.username,
				"email":    "test@example.com",
				"password": "Password123!",
			}

			body, _ := json.Marshal(registerData)
			request := createRegisterRequest(string(body))
			request.Header.Set("Content-Type", "application/json")
			recorder := httptest.NewRecorder()

			handler.Register(recorder, request)

			testutils.AssertHTTPStatus(t, recorder, tt.expectedStatus)

			expectedUsername := security.SanitizeUsername(tt.username)
			if capturedUsername != expectedUsername {
				t.Errorf("Expected sanitized username %q, got %q", expectedUsername, capturedUsername)
			}
		})
	}
}

func TestPostHandler_AuthorizationBypass(t *testing.T) {
	repo := &testutils.PostRepositoryStub{
		GetByIDFn: func(id uint) (*database.Post, error) {
			authorID := uint(2)
			return &database.Post{ID: id, Title: "Test Post", AuthorID: &authorID}, nil
		},
	}

	handler := NewPostHandler(repo, nil, nil)

	updateData := map[string]string{
		"title":   "Updated Title",
		"content": "Updated content",
	}

	body, _ := json.Marshal(updateData)
	request := httptest.NewRequest("PUT", "/api/posts/1", bytes.NewBuffer(body))
	request.Header.Set("Content-Type", "application/json")
	request = request.WithContext(context.WithValue(request.Context(), middleware.UserIDKey, uint(1)))

	routeCtx := chi.NewRouteContext()
	routeCtx.URLParams.Add("id", "1")
	request = request.WithContext(context.WithValue(request.Context(), chi.RouteCtxKey, routeCtx))

	recorder := httptest.NewRecorder()

	handler.UpdatePost(recorder, request)

	if recorder.Result().StatusCode != http.StatusForbidden {
		t.Errorf("Expected status 403, got %d. Users should not be able to edit other users' posts", recorder.Result().StatusCode)
	}
}

func TestPageHandler_PasswordResetValidation(t *testing.T) {

	tests := []struct {
		name          string
		password      string
		expectedError bool
		description   string
	}{
		{
			name:          "valid password",
			password:      "Password123!",
			expectedError: false,
			description:   "Valid passwords should pass validation",
		},
		{
			name:          "password without special chars",
			password:      "Password123",
			expectedError: true,
			description:   "Passwords without special characters should be rejected",
		},
		{
			name:          "password too short",
			password:      "Pass1!",
			expectedError: true,
			description:   "Passwords shorter than 8 characters should be rejected",
		},
		{
			name:          "password without letters",
			password:      "12345678!",
			expectedError: true,
			description:   "Passwords without letters should be rejected",
		},
		{
			name:          "password without numbers",
			password:      "Password!",
			expectedError: true,
			description:   "Passwords without numbers should be rejected",
		},
		{
			name:          "empty password",
			password:      "",
			expectedError: true,
			description:   "Empty passwords should be rejected",
		},
		{
			name:          "password too long",
			password:      string(make([]byte, 129)),
			expectedError: true,
			description:   "Passwords longer than 128 characters should be rejected",
		},
		{
			name:          "valid password with unicode",
			password:      "Pássw0rd123!",
			expectedError: false,
			description:   "Valid passwords with unicode should pass validation",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validation.ValidatePassword(tt.password)

			if tt.expectedError && err == nil {
				t.Errorf("ValidatePassword(%q) expected error, got nil. %s", tt.password, tt.description)
			}
			if !tt.expectedError && err != nil {
				t.Errorf("ValidatePassword(%q) unexpected error: %v. %s", tt.password, err, tt.description)
			}
		})
	}
}
