package handlers

import (
	"encoding/json"
	"net/http"
	"testing"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/dto/requests"
	"github.com/treytartt/honeydue-api/internal/repositories"
	"github.com/treytartt/honeydue-api/internal/services"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

func setupAuthHandler(t *testing.T) (*AuthHandler, *echo.Echo, *repositories.UserRepository) {
	db := testutil.SetupTestDB(t)
	userRepo := repositories.NewUserRepository(db)
	cfg := &config.Config{
		Security: config.SecurityConfig{
			SecretKey:            "test-secret-key",
			PasswordResetExpiry:  15 * time.Minute,
			ConfirmationExpiry:   24 * time.Hour,
			MaxPasswordResetRate: 3,
		},
	}
	authService := services.NewAuthService(userRepo, cfg)
	handler := NewAuthHandler(authService, nil, nil) // No email or cache for tests
	e := testutil.SetupTestRouter()
	return handler, e, userRepo
}

func TestAuthHandler_Register(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/register/", handler.Register)

	t.Run("successful registration", func(t *testing.T) {
		req := requests.RegisterRequest{
			Username:  "newuser",
			Email:     "new@test.com",
			Password:  "Password123",
			FirstName: "New",
			LastName:  "User",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusCreated)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		testutil.AssertJSONFieldExists(t, response, "token")
		testutil.AssertJSONFieldExists(t, response, "user")
		testutil.AssertJSONFieldExists(t, response, "message")

		user := response["user"].(map[string]interface{})
		assert.Equal(t, "newuser", user["username"])
		assert.Equal(t, "new@test.com", user["email"])
		assert.Equal(t, "New", user["first_name"])
		assert.Equal(t, "User", user["last_name"])
	})

	t.Run("registration with missing fields", func(t *testing.T) {
		req := map[string]string{
			"username": "test",
			// Missing email and password
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusBadRequest)

		response := testutil.ParseJSON(t, w.Body.Bytes())
		testutil.AssertJSONFieldExists(t, response, "error")
	})

	t.Run("registration with short password", func(t *testing.T) {
		req := requests.RegisterRequest{
			Username: "testuser",
			Email:    "test@test.com",
			Password: "short", // Less than 8 chars
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})

	t.Run("registration with duplicate username", func(t *testing.T) {
		// First registration
		req := requests.RegisterRequest{
			Username: "duplicate",
			Email:    "unique1@test.com",
			Password: "Password123",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusCreated)

		// Try to register again with same username
		req.Email = "unique2@test.com"
		w = testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusConflict) // 409 for duplicate resource

		response := testutil.ParseJSON(t, w.Body.Bytes())
		assert.Contains(t, response["error"], "Username already taken")
	})

	t.Run("registration with duplicate email", func(t *testing.T) {
		// First registration
		req := requests.RegisterRequest{
			Username: "user1",
			Email:    "duplicate@test.com",
			Password: "Password123",
		}
		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusCreated)

		// Try to register again with same email
		req.Username = "user2"
		w = testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")
		testutil.AssertStatusCode(t, w, http.StatusConflict) // 409 for duplicate resource

		response := testutil.ParseJSON(t, w.Body.Bytes())
		assert.Contains(t, response["error"], "Email already registered")
	})
}

func TestAuthHandler_Login(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/register/", handler.Register)
	e.POST("/api/auth/login/", handler.Login)

	// Create a test user
	registerReq := requests.RegisterRequest{
		Username:  "logintest",
		Email:     "login@test.com",
		Password:  "Password123",
		FirstName: "Test",
		LastName:  "User",
	}
	w := testutil.MakeRequest(e, "POST", "/api/auth/register/", registerReq, "")
	testutil.AssertStatusCode(t, w, http.StatusCreated)

	t.Run("successful login with username", func(t *testing.T) {
		req := requests.LoginRequest{
			Username: "logintest",
			Password: "Password123",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		testutil.AssertJSONFieldExists(t, response, "token")
		testutil.AssertJSONFieldExists(t, response, "user")

		user := response["user"].(map[string]interface{})
		assert.Equal(t, "logintest", user["username"])
		assert.Equal(t, "login@test.com", user["email"])
	})

	t.Run("successful login with email", func(t *testing.T) {
		req := requests.LoginRequest{
			Username: "login@test.com", // Using email as username
			Password: "Password123",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusOK)
	})

	t.Run("login with wrong password", func(t *testing.T) {
		req := requests.LoginRequest{
			Username: "logintest",
			Password: "wrongpassword",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusUnauthorized)

		response := testutil.ParseJSON(t, w.Body.Bytes())
		assert.Contains(t, response["error"], "Invalid credentials")
	})

	t.Run("login with non-existent user", func(t *testing.T) {
		req := requests.LoginRequest{
			Username: "nonexistent",
			Password: "Password123",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusUnauthorized)
	})

	t.Run("login with missing fields", func(t *testing.T) {
		req := map[string]string{
			"username": "logintest",
			// Missing password
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/login/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusBadRequest)
	})
}

func TestAuthHandler_CurrentUser(t *testing.T) {
	handler, e, userRepo := setupAuthHandler(t)

	db := testutil.SetupTestDB(t)
	user := testutil.CreateTestUser(t, db, "metest", "me@test.com", "Password123")
	user.FirstName = "Test"
	user.LastName = "User"
	userRepo.Update(user)

	// Set up route with mock auth middleware
	authGroup := e.Group("/api/auth")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.GET("/me/", handler.CurrentUser)

	t.Run("get current user", func(t *testing.T) {
		w := testutil.MakeRequest(e, "GET", "/api/auth/me/", nil, "test-token")

		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Equal(t, "metest", response["username"])
		assert.Equal(t, "me@test.com", response["email"])
	})
}

func TestAuthHandler_UpdateProfile(t *testing.T) {
	handler, e, userRepo := setupAuthHandler(t)

	db := testutil.SetupTestDB(t)
	user := testutil.CreateTestUser(t, db, "updatetest", "update@test.com", "Password123")
	userRepo.Update(user)

	authGroup := e.Group("/api/auth")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.PUT("/profile/", handler.UpdateProfile)

	t.Run("update profile", func(t *testing.T) {
		firstName := "Updated"
		lastName := "Name"
		req := requests.UpdateProfileRequest{
			FirstName: &firstName,
			LastName:  &lastName,
		}

		w := testutil.MakeRequest(e, "PUT", "/api/auth/profile/", req, "test-token")

		testutil.AssertStatusCode(t, w, http.StatusOK)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Equal(t, "Updated", response["first_name"])
		assert.Equal(t, "Name", response["last_name"])
	})
}

func TestAuthHandler_ForgotPassword(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/register/", handler.Register)
	e.POST("/api/auth/forgot-password/", handler.ForgotPassword)

	// Create a test user
	registerReq := requests.RegisterRequest{
		Username: "forgottest",
		Email:    "forgot@test.com",
		Password: "Password123",
	}
	testutil.MakeRequest(e, "POST", "/api/auth/register/", registerReq, "")

	t.Run("forgot password with valid email", func(t *testing.T) {
		req := requests.ForgotPasswordRequest{
			Email: "forgot@test.com",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/forgot-password/", req, "")

		// Always returns 200 to prevent email enumeration
		testutil.AssertStatusCode(t, w, http.StatusOK)

		response := testutil.ParseJSON(t, w.Body.Bytes())
		testutil.AssertJSONFieldExists(t, response, "message")
	})

	t.Run("forgot password with invalid email", func(t *testing.T) {
		req := requests.ForgotPasswordRequest{
			Email: "nonexistent@test.com",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/forgot-password/", req, "")

		// Still returns 200 to prevent email enumeration
		testutil.AssertStatusCode(t, w, http.StatusOK)
	})
}

func TestAuthHandler_Logout(t *testing.T) {
	handler, e, userRepo := setupAuthHandler(t)

	db := testutil.SetupTestDB(t)
	user := testutil.CreateTestUser(t, db, "logouttest", "logout@test.com", "Password123")
	userRepo.Update(user)

	authGroup := e.Group("/api/auth")
	authGroup.Use(testutil.MockAuthMiddleware(user))
	authGroup.POST("/logout/", handler.Logout)

	t.Run("successful logout", func(t *testing.T) {
		w := testutil.MakeRequest(e, "POST", "/api/auth/logout/", nil, "test-token")

		testutil.AssertStatusCode(t, w, http.StatusOK)

		response := testutil.ParseJSON(t, w.Body.Bytes())
		assert.Contains(t, response["message"], "Logged out successfully")
	})
}

func TestAuthHandler_JSONResponses(t *testing.T) {
	handler, e, _ := setupAuthHandler(t)

	e.POST("/api/auth/register/", handler.Register)
	e.POST("/api/auth/login/", handler.Login)

	t.Run("register response has correct JSON structure", func(t *testing.T) {
		req := requests.RegisterRequest{
			Username:  "jsontest",
			Email:     "json@test.com",
			Password:  "Password123",
			FirstName: "JSON",
			LastName:  "Test",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusCreated)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		// Verify top-level structure
		assert.Contains(t, response, "token")
		assert.Contains(t, response, "user")
		assert.Contains(t, response, "message")

		// Verify token is not empty
		assert.NotEmpty(t, response["token"])

		// Verify user structure
		user := response["user"].(map[string]interface{})
		assert.Contains(t, user, "id")
		assert.Contains(t, user, "username")
		assert.Contains(t, user, "email")
		assert.Contains(t, user, "first_name")
		assert.Contains(t, user, "last_name")
		assert.Contains(t, user, "is_active")
		assert.Contains(t, user, "date_joined")

		// Verify types
		assert.IsType(t, float64(0), user["id"]) // JSON numbers are float64
		assert.IsType(t, "", user["username"])
		assert.IsType(t, "", user["email"])
		assert.IsType(t, true, user["is_active"])
	})

	t.Run("error response has correct JSON structure", func(t *testing.T) {
		req := map[string]string{
			"username": "test",
		}

		w := testutil.MakeRequest(e, "POST", "/api/auth/register/", req, "")

		testutil.AssertStatusCode(t, w, http.StatusBadRequest)

		var response map[string]interface{}
		err := json.Unmarshal(w.Body.Bytes(), &response)
		require.NoError(t, err)

		assert.Contains(t, response, "error")
		assert.IsType(t, "", response["error"])
	})
}
