package handlers

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

	"veza-backend-api/internal/services"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"go.uber.org/zap"
)

// MockPasswordResetService mocks PasswordResetService
type MockPasswordResetService struct {
	mock.Mock
}

func (m *MockPasswordResetService) GenerateToken() (string, error) {
	args := m.Called()
	return args.String(0), args.Error(1)
}

func (m *MockPasswordResetService) StoreToken(userID uuid.UUID, token string) error {
	args := m.Called(userID, token)
	return args.Error(0)
}

func (m *MockPasswordResetService) VerifyToken(token string) (uuid.UUID, error) {
	args := m.Called(token)
	return args.Get(0).(uuid.UUID), args.Error(1)
}

func (m *MockPasswordResetService) MarkTokenAsUsed(token string) error {
	args := m.Called(token)
	return args.Error(0)
}

func (m *MockPasswordResetService) InvalidateOldTokens(userID uuid.UUID) error {
	args := m.Called(userID)
	return args.Error(0)
}

// MockPasswordService mocks PasswordService
type MockPasswordService struct {
	mock.Mock
}

func (m *MockPasswordService) GetUserByEmail(email string) (*services.UserInfo, error) {
	args := m.Called(email)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*services.UserInfo), args.Error(1)
}

func (m *MockPasswordService) ValidatePassword(password string) error {
	args := m.Called(password)
	return args.Error(0)
}

func (m *MockPasswordService) UpdatePassword(userID uuid.UUID, password string) error {
	args := m.Called(userID, password)
	return args.Error(0)
}

// MockEmailService mocks EmailService
type MockEmailService struct {
	mock.Mock
}

func (m *MockEmailService) SendPasswordResetEmail(userID uuid.UUID, email, token string) error {
	args := m.Called(userID, email, token)
	return args.Error(0)
}

// MockAuditService mocks AuditService
type MockAuditService struct {
	mock.Mock
}

func (m *MockAuditService) LogPasswordResetRequest(ctx context.Context, userID *uuid.UUID, email, ip, userAgent string) error {
	args := m.Called(ctx, userID, email, ip, userAgent)
	return args.Error(0)
}

func (m *MockAuditService) LogPasswordReset(ctx context.Context, userID uuid.UUID, success bool, ip, userAgent string) error {
	args := m.Called(ctx, userID, success, ip, userAgent)
	return args.Error(0)
}

// MockAuthService mocks AuthService
type MockAuthService struct {
	mock.Mock
}

func (m *MockAuthService) InvalidateAllUserSessions(ctx context.Context, userID uuid.UUID, sessionService SessionServiceInterface) error {
	args := m.Called(ctx, userID, sessionService)
	return args.Error(0)
}

func setupTestPasswordResetRouter(
	mockPasswordResetService *MockPasswordResetService,
	mockPasswordService *MockPasswordService,
	mockEmailService *MockEmailService,
	mockAuditService *MockAuditService,
	mockAuthService *MockAuthService,
) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	logger := zap.NewNop()

	api := router.Group("/api/v1/auth/password")
	{
		api.POST("/reset-request", RequestPasswordResetWithInterfaces(
			mockPasswordResetService,
			mockPasswordService,
			mockEmailService,
			mockAuditService,
			logger,
		))
		api.POST("/reset", ResetPasswordWithInterfaces(
			mockPasswordResetService,
			mockPasswordService,
			mockAuthService,
			nil, // sessionService - can be nil for these tests
			mockAuditService,
			logger,
		))
	}

	return router
}

func TestRequestPasswordReset_Success(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	userID := uuid.New()
	mockUser := &services.UserInfo{
		ID:    userID,
		Email: "test@example.com",
	}
	token := "test-reset-token"

	reqBody := RequestPasswordResetRequest{
		Email: "test@example.com",
	}

	mockPasswordService.On("GetUserByEmail", "test@example.com").Return(mockUser, nil)
	mockPasswordResetService.On("InvalidateOldTokens", userID).Return(nil)
	mockPasswordResetService.On("GenerateToken").Return(token, nil)
	mockPasswordResetService.On("StoreToken", userID, token).Return(nil)
	mockEmailService.On("SendPasswordResetEmail", userID, "test@example.com", token).Return(nil)
	mockAuditService.On("LogPasswordResetRequest", mock.Anything, &userID, "test@example.com", mock.Anything, mock.Anything).Return(nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset-request", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockPasswordService.AssertExpectations(t)
	mockPasswordResetService.AssertExpectations(t)
}

func TestRequestPasswordReset_UserNotFound(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	reqBody := RequestPasswordResetRequest{
		Email: "notfound@example.com",
	}

	mockPasswordService.On("GetUserByEmail", "notfound@example.com").Return(nil, assert.AnError)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset-request", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert - Should return success for security (prevent email enumeration)
	assert.Equal(t, http.StatusOK, w.Code)
	mockPasswordResetService.AssertNotCalled(t, "GenerateToken")
}

func TestRequestPasswordReset_InvalidEmail(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	reqBody := map[string]string{"email": "invalid-email"}

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset-request", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockPasswordService.AssertNotCalled(t, "GetUserByEmail")
}

func TestResetPassword_Success(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	userID := uuid.New()
	token := "valid-token"
	newPassword := "newPassword123"

	reqBody := ResetPasswordRequest{
		Token:       token,
		NewPassword: newPassword,
	}

	mockPasswordResetService.On("VerifyToken", token).Return(userID, nil)
	mockPasswordService.On("ValidatePassword", newPassword).Return(nil)
	mockPasswordService.On("UpdatePassword", userID, newPassword).Return(nil)
	mockPasswordResetService.On("MarkTokenAsUsed", token).Return(nil)
	mockAuthService.On("InvalidateAllUserSessions", mock.Anything, userID, mock.Anything).Return(nil)
	mockAuditService.On("LogPasswordReset", mock.Anything, userID, true, mock.Anything, mock.Anything).Return(nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusOK, w.Code)
	mockPasswordResetService.AssertExpectations(t)
	mockPasswordService.AssertExpectations(t)
}

func TestResetPassword_InvalidToken(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	token := "invalid-token"
	newPassword := "newPassword123"

	reqBody := ResetPasswordRequest{
		Token:       token,
		NewPassword: newPassword,
	}

	mockPasswordResetService.On("VerifyToken", token).Return(uuid.Nil, assert.AnError)
	mockAuditService.On("LogPasswordReset", mock.Anything, uuid.Nil, false, mock.Anything, mock.Anything).Return(nil)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockPasswordService.AssertNotCalled(t, "UpdatePassword")
}

func TestResetPassword_InvalidPassword(t *testing.T) {
	// Setup
	mockPasswordResetService := new(MockPasswordResetService)
	mockPasswordService := new(MockPasswordService)
	mockEmailService := new(MockEmailService)
	mockAuditService := new(MockAuditService)
	mockAuthService := new(MockAuthService)
	router := setupTestPasswordResetRouter(
		mockPasswordResetService,
		mockPasswordService,
		mockEmailService,
		mockAuditService,
		mockAuthService,
	)

	userID := uuid.New()
	token := "valid-token"
	newPassword := "short"

	reqBody := ResetPasswordRequest{
		Token:       token,
		NewPassword: newPassword,
	}

	mockPasswordResetService.On("VerifyToken", token).Return(userID, nil)
	mockPasswordService.On("ValidatePassword", newPassword).Return(assert.AnError)

	body, _ := json.Marshal(reqBody)

	// Execute
	req, _ := http.NewRequest("POST", "/api/v1/auth/password/reset", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Assert
	assert.Equal(t, http.StatusBadRequest, w.Code)
	mockPasswordService.AssertNotCalled(t, "UpdatePassword")
}
