package auth

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

	"veza-backend-api/internal/dto"
	"veza-backend-api/internal/models"

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

func setupTestAuthHandler(t *testing.T) (*AuthHandler, *gin.Engine, *TestMocks, func()) {
	service, _, mocks, cleanupService := setupTestAuthService(t)

	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAuthHandler(
		service,
		nil, // sessionService
		zaptest.NewLogger(t),
	)

	return handler, router, mocks, func() {
		cleanupService()
	}
}

func expectRegister(mocks *TestMocks) {
	mocks.EmailVerification.On("GenerateToken").Return("verification-token", nil).Maybe()
	mocks.EmailVerification.On("StoreToken", mock.Anything, mock.Anything, "verification-token").Return(nil).Maybe()
	mocks.JWT.On("GenerateAccessToken", mock.AnythingOfType("*models.User")).Return("access-token", nil).Once()
	mocks.JWT.On("GenerateRefreshToken", mock.AnythingOfType("*models.User")).Return("refresh-token", nil).Once()
	mocks.RefreshToken.On("Store", mock.Anything, "refresh-token", mock.Anything).Return(nil).Once()
}

func TestAuthHandler_Register_Success(t *testing.T) {
	handler, router, mocks, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	router.POST("/register", handler.Register)

	reqBody := dto.RegisterRequest{
		Email:           "handler@example.com",
		Username:        "handleruser",
		Password:        "StrongPassword123!",
		PasswordConfirm: "StrongPassword123!",
	}
	body, _ := json.Marshal(reqBody)

	expectRegister(mocks)

	req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)

	var resp dto.RegisterResponse
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)
	assert.Equal(t, reqBody.Email, resp.User.Email)
	assert.Equal(t, reqBody.Username, resp.User.Username)
	assert.NotEmpty(t, resp.Token.AccessToken)
}

func TestAuthHandler_Login_Success(t *testing.T) {
	handler, router, mocks, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	router.POST("/login", handler.Login)

	// Pre-register user directly via service
	ctx := context.Background()

	expectRegister(mocks)

	_, _, err := handler.authService.Register(ctx, "login_h@example.com", "login_h", "StrongPassword123!")
	require.NoError(t, err)

	reqBody := dto.LoginRequest{
		Email:    "login_h@example.com",
		Password: "StrongPassword123!",
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	// Login expectations
	mocks.JWT.On("GenerateAccessToken", mock.AnythingOfType("*models.User")).Return("new-access-token", nil).Once()
	mocks.JWT.On("GenerateRefreshToken", mock.AnythingOfType("*models.User")).Return("new-refresh-token", nil).Once()
	mocks.RefreshToken.On("Store", mock.Anything, "new-refresh-token", mock.Anything).Return(nil).Once()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp dto.LoginResponse
	err = json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)
	assert.Equal(t, reqBody.Email, resp.User.Email)
	assert.NotEmpty(t, resp.Token.AccessToken)
}

func TestAuthHandler_Login_InvalidCredentials(t *testing.T) {
	handler, router, _, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	router.POST("/login", handler.Login)

	reqBody := dto.LoginRequest{
		Email:    "nonexistent@example.com",
		Password: "StrongPassword123!",
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusUnauthorized, w.Code)
}

func TestAuthHandler_Refresh_Success(t *testing.T) {
	handler, _, mocks, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	expectRegister(mocks)

	// Register via service
	ctx := context.Background()
	user, tokenPair, err := handler.authService.Register(ctx, "refresh_h@example.com", "refresh_h", "StrongPassword123!")
	require.NoError(t, err)

	reqBody := dto.RefreshRequest{
		RefreshToken: tokenPair.RefreshToken,
	}
	body, _ := json.Marshal(reqBody)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("POST", "/refresh", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	// Refresh expectations
	claims := &models.CustomClaims{UserID: user.ID, IsRefresh: true}
	mocks.JWT.On("ValidateToken", tokenPair.RefreshToken).Return(claims, nil)
	mocks.RefreshToken.On("Validate", user.ID, tokenPair.RefreshToken).Return(nil)
	mocks.JWT.On("GenerateAccessToken", mock.AnythingOfType("*models.User")).Return("refreshed-access-token", nil)
	mocks.JWT.On("GenerateRefreshToken", mock.AnythingOfType("*models.User")).Return("refreshed-refresh-token", nil)
	mocks.RefreshToken.On("Rotate", user.ID, tokenPair.RefreshToken, "refreshed-refresh-token", mock.Anything).Return(nil)

	handler.Refresh(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp dto.TokenResponse
	err = json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)
	assert.NotEmpty(t, resp.AccessToken)
}

func TestAuthHandler_CheckUsername_Available(t *testing.T) {
	handler, _, _, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("GET", "/check-username?username=newuser_check", nil)

	handler.CheckUsername(c)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)

	val, ok := resp["available"]
	assert.True(t, ok)
	assert.Equal(t, true, val)
}

func TestAuthHandler_GetMe_Success(t *testing.T) {
	handler, _, mocks, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	ctx := context.Background()
	expectRegister(mocks)

	user, _, err := handler.authService.Register(ctx, "me@example.com", "meuser", "StrongPassword123!")
	require.NoError(t, err)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("GET", "/me", nil)

	c.Set("user_id", user.ID)
	c.Set("email", user.Email)
	c.Set("role", user.Role)

	handler.GetMe(c)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &resp)
	require.NoError(t, err)
	assert.Equal(t, user.Email, resp["email"])
}

func TestAuthHandler_Logout_Success(t *testing.T) {
	handler, _, mocks, cleanup := setupTestAuthHandler(t)
	defer cleanup()

	ctx := context.Background()
	expectRegister(mocks)

	user, tokenPair, err := handler.authService.Register(ctx, "logout_h@example.com", "logout_h", "StrongPassword123!")
	require.NoError(t, err)

	reqBody := struct {
		RefreshToken string `json:"refresh_token"`
	}{
		RefreshToken: tokenPair.RefreshToken,
	}
	body, _ := json.Marshal(reqBody)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request, _ = http.NewRequest("POST", "/logout", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	c.Set("user_id", user.ID)

	// Logout expectations
	claims := &models.CustomClaims{UserID: user.ID}
	mocks.JWT.On("ValidateToken", tokenPair.RefreshToken).Return(claims, nil)
	mocks.RefreshToken.On("Revoke", user.ID, tokenPair.RefreshToken).Return(nil)

	handler.Logout(c)

	assert.Equal(t, http.StatusOK, w.Code)
}
