require "test_helper"

class AuthenticationFlowTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:admin_user)
    @user.update!(invitation_accepted_at: Time.current)
    Rails.cache.clear
  end

  test "user can sign in with valid credentials" do
    get login_path
    assert_response :success

    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    assert_redirected_to admin_root_path
    follow_redirect!
    assert_response :success
    assert_equal @user.id, session[:user_id]
  end

  test "user cannot sign in with invalid password" do
    post login_path, params: {
      email: @user.email,
      password: "wrongpassword"
    }

    assert_response :unprocessable_entity
    assert_nil session[:user_id]
    assert_select "div[role='alert']", text: /invalid email or password/i
  end

  test "user cannot sign in with non-existent email" do
    post login_path, params: {
      email: "nonexistent@example.com",
      password: "password123456"
    }

    assert_response :unprocessable_entity
    assert_nil session[:user_id]
  end

  test "pending user cannot sign in" do
    pending_user = users(:pending_invitation)

    post login_path, params: {
      email: pending_user.email,
      password: "password123456"
    }

    assert_response :unprocessable_entity
    assert_nil session[:user_id]
    assert_select "div[role='alert']", text: /pending/i
  end

  test "user can sign out" do
    # Sign in first
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }
    assert_equal @user.id, session[:user_id]

    # Sign out
    delete logout_path

    assert_redirected_to root_path
    assert_nil session[:user_id]
  end

  test "session persists across requests" do
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    assert_equal @user.id, session[:user_id]

    get root_path
    assert_equal @user.id, session[:user_id]

    get entries_path
    assert_equal @user.id, session[:user_id]
  end

  test "remember me creates cookie" do
    post login_path, params: {
      email: @user.email,
      password: "password123456",
      remember_me: "1"
    }

    assert_not_nil cookies[:remember_token]
    @user.reload
    assert_not_nil @user.remember_token
    assert_not_nil @user.remember_created_at
  end

  test "remember me cookie logs user in automatically" do
    post login_path, params: {
      email: @user.email,
      password: "password123456",
      remember_me: "1"
    }
    remember_cookie = cookies[:remember_token]
    assert_not_nil remember_cookie

    # Clear session
    reset!

    # Make request with remember cookie
    cookies[:remember_token] = remember_cookie
    get root_path

    assert_equal @user.id, session[:user_id]
  end

  test "logout clears remember me cookie" do
    # Sign in with remember me
    post login_path, params: {
      email: @user.email,
      password: "password123456",
      remember_me: "1"
    }

    assert_not_nil cookies[:remember_token]

    # Sign out
    delete logout_path

    remember_cookie = cookies[:remember_token]
    assert remember_cookie.nil? || remember_cookie.empty?
    @user.reload
    assert_nil @user.remember_token
  end

  test "rate limiting prevents brute force" do
    max_attempts = 5
    responses = []

    (max_attempts + 1).times do
      post login_path, params: {
        email: @user.email,
        password: "wrongpassword"
      }
      responses << response.status
    end

    assert responses.first(max_attempts).all? { |status| status == 422 },
           "Expected first #{max_attempts} responses to be 422, got #{responses.first(max_attempts)}"

    if Rails.cache.is_a?(ActiveSupport::Cache::NullStore)
      assert responses.last == 422,
             "Expected last response to be 422 with null cache store, got #{responses.last}"
    else
      assert responses.last == 429, "Expected last response to be 429, got #{responses.last}"
    end
    assert_select "div[role='alert']", text: /too many/i
  end

  test "successful login resets rate limit" do
    # Fail a few times
    3.times do
      post login_path, params: {
        email: @user.email,
        password: "wrongpassword"
      }
    end

    # Then succeed
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    assert_redirected_to admin_root_path

    # Should be able to login again immediately
    delete logout_path
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    assert_redirected_to admin_root_path
  end

  test "session timeout logs user out after inactivity" do
    # Sign in
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }
    assert_equal @user.id, session[:user_id]

    # Simulate time passing
    travel 4.days do
      get root_path
      assert_redirected_to login_path
      assert_match /expired/i, flash[:alert]
      assert_nil session[:user_id]
    end
  end

  test "remember me prevents session timeout" do
    # Sign in with remember me
    post login_path, params: {
      email: @user.email,
      password: "password123456",
      remember_me: "1"
    }

    remember_cookie = cookies[:remember_token]

    # Simulate time passing (but within remember me period)
    travel 5.days do
      cookies[:remember_token] = remember_cookie
      get root_path
      assert_response :success
      assert_equal @user.id, session[:user_id]
    end
  end

  test "password reset flow completes successfully" do
    # Request reset
    post password_resets_path, params: { email: @user.email }
    assert_redirected_to login_path

    @user.reload
    assert_not_nil @user.reset_password_token

    # Visit reset form
    get edit_password_reset_path(@user.reset_password_token)
    assert_response :success

    # Submit new password
    patch password_reset_path(@user.reset_password_token), params: {
      password: "newpassword12345",
      password_confirmation: "newpassword12345"
    }

    assert_redirected_to root_path
    assert_equal @user.id, session[:user_id]

    @user.reload
    assert @user.authenticate("newpassword12345")
    assert_nil @user.reset_password_token
  end

  test "invitation acceptance flow" do
    pending_user = users(:pending_invitation)

    # Visit invitation
    get invitation_path(pending_user.invitation_token)
    assert_response :success

    # Accept invitation
    patch accept_invitation_path(pending_user.invitation_token), params: {
      user: {
        password: "newpassword12345",
        password_confirmation: "newpassword12345"
      }
    }

    assert_redirected_to root_path
    assert_equal pending_user.id, session[:user_id]

    pending_user.reload
    assert_not_nil pending_user.invitation_accepted_at
    assert pending_user.authenticate("newpassword12345")
  end

  test "expired invitation cannot be accepted" do
    pending_user = users(:pending_invitation)
    pending_user.update!(invitation_sent_at: 15.days.ago)

    get invitation_path(pending_user.invitation_token)
    assert_redirected_to root_path
    assert_match /expired/i, flash[:alert]
  end

  test "admin user redirects to admin dashboard after login" do
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    assert_redirected_to admin_root_path
  end

  test "contributor redirects to root after login" do
    contributor = users(:contributor_user)
    contributor.update!(invitation_accepted_at: Time.current)

    post login_path, params: {
      email: contributor.email,
      password: "password123456"
    }

    assert_redirected_to root_path
  end

  test "already logged in user redirects from login page" do
    # Sign in first
    post login_path, params: {
      email: @user.email,
      password: "password123456"
    }

    # Try to visit login page again
    get login_path
    assert_redirected_to admin_root_path
  end
end
