require "test_helper"

class Admin::InvitationsControllerTest < ActionDispatch::IntegrationTest
  test "should redirect to login when not authenticated" do
    get admin_invitations_path
    assert_redirected_to login_path
  end

  test "should redirect to root when logged in as non-admin" do
    login_as(users(:contributor_user))
    get admin_invitations_path
    assert_redirected_to root_path
  end

  test "should show invitations index when logged in as admin" do
    login_as(users(:admin_user))
    get admin_invitations_path
    assert_response :success
  end

  test "should get new invitation page when logged in as admin" do
    login_as(users(:admin_user))
    get new_admin_invitation_path
    assert_response :success
    assert_select "form"
    assert_select "input[name='user[email]']"
    assert_select "input[name='user[name]']"
    assert_select "select[name='user[role]']"
    assert_select "select[name='user[primary_language]']"
  end

  test "should display pending invitations on index page" do
    login_as(users(:admin_user))
    get admin_invitations_path
    assert_response :success
    assert_select "h1,h2", /Invitations/
  end

  test "should create invitation when logged in as admin" do
    login_as(users(:admin_user))

    assert_difference("User.count", 1) do
      post admin_invitations_path, params: {
        user: {
          email: "newuser@example.com",
          name: "New User",
          role: "contributor",
          primary_language: "en"
        }
      }
    end

    assert_redirected_to admin_invitations_path

    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
    assert_not_nil new_user.invitation_token
    assert_not_nil new_user.invitation_sent_at
    assert_nil new_user.invitation_accepted_at
    assert_equal users(:admin_user).id, new_user.invited_by_id
  end

  test "should send invitation email when creating invitation" do
    login_as(users(:admin_user))

    assert_enqueued_emails 1 do
      post admin_invitations_path, params: {
        user: {
          email: "newuser@example.com",
          name: "New User",
          role: "contributor",
          primary_language: "en"
        }
      }
    end
  end

  test "should not create invitation with invalid data" do
    login_as(users(:admin_user))

    assert_no_difference("User.count") do
      post admin_invitations_path, params: {
        user: {
          email: "",
          name: "New User",
          role: "contributor",
          primary_language: "en"
        }
      }
    end

    assert_response :unprocessable_entity
  end

  test "should cancel pending invitation when logged in as admin" do
    login_as(users(:admin_user))

    assert_difference("User.count", -1) do
      delete admin_invitation_path(users(:pending_invitation))
    end

    assert_redirected_to admin_invitations_path
  end

  test "should not cancel accepted invitation" do
    login_as(users(:admin_user))

    assert_no_difference("User.count") do
      delete admin_invitation_path(users(:contributor_user))
    end

    assert_redirected_to admin_invitations_path
    follow_redirect!
    assert_select ".bg-red-50", /Cannot cancel an accepted invitation/
  end

  test "should not allow non-admin to create invitation" do
    login_as(users(:contributor_user))

    assert_no_difference("User.count") do
      post admin_invitations_path, params: {
        user: {
          email: "newuser@example.com",
          name: "New User",
          role: "contributor",
          primary_language: "en"
        }
      }
    end

    assert_redirected_to root_path
  end

  test "should not allow non-admin to cancel invitation" do
    login_as(users(:contributor_user))

    assert_no_difference("User.count") do
      delete admin_invitation_path(users(:pending_invitation))
    end

    assert_redirected_to root_path
  end

  # Resend action tests
  test "should resend invitation for pending invitation" do
    login_as(users(:admin_user))
    pending_user = users(:pending_invitation)
    original_token = pending_user.invitation_token
    original_sent_at = pending_user.invitation_sent_at

    assert_enqueued_emails 1 do
      put resend_admin_invitation_path(pending_user)
    end

    assert_redirected_to admin_invitations_path
    assert_match /Invitation resent to #{pending_user.email}/, flash[:notice]

    pending_user.reload
    assert_not_equal original_token, pending_user.invitation_token
    assert_operator pending_user.invitation_sent_at, :>, original_sent_at
  end

  test "should not resend accepted invitation" do
    login_as(users(:admin_user))
    accepted_user = users(:contributor_user)

    assert_enqueued_emails 0 do
      put resend_admin_invitation_path(accepted_user)
    end

    assert_redirected_to admin_invitations_path
    assert_equal "Cannot resend an accepted invitation.", flash[:alert]
  end

  test "should update invitation_sent_at when resending" do
    login_as(users(:admin_user))
    pending_user = users(:pending_invitation)

    freeze_time do
      put resend_admin_invitation_path(pending_user)

      pending_user.reload
      assert_in_delta Time.current.to_i, pending_user.invitation_sent_at.to_i, 2
    end
  end

  test "should generate new token when resending" do
    login_as(users(:admin_user))
    pending_user = users(:pending_invitation)
    original_token = pending_user.invitation_token

    put resend_admin_invitation_path(pending_user)

    pending_user.reload
    assert_not_nil pending_user.invitation_token
    assert_not_equal original_token, pending_user.invitation_token
    assert pending_user.invitation_token.length >= 32
  end

  test "should not allow non-admin to resend invitation" do
    login_as(users(:contributor_user))

    put resend_admin_invitation_path(users(:pending_invitation))

    assert_redirected_to root_path
  end

  test "should require authentication to resend invitation" do
    put resend_admin_invitation_path(users(:pending_invitation))

    assert_redirected_to login_path
  end

  # Index action tests
  test "should list pending invitations in descending order by sent date" do
    login_as(users(:admin_user))

    # Create multiple pending invitations
    user1 = User.create!(email: "user1@example.com", name: "User 1", password: "temp123456789")
    user1.invite_by!(users(:admin_user))

    user2 = User.create!(email: "user2@example.com", name: "User 2", password: "temp123456789")
    user2.update_columns(invitation_token: "token2", invitation_sent_at: 1.hour.ago)

    get admin_invitations_path

    assert_response :success
    assert_select "h3", /Pending Invitations/i
    # Check both emails appear in the page
    assert_select "td", text: "user1@example.com"
    assert_select "td", text: "user2@example.com"
  end

  test "should display accepted invitations section" do
    login_as(users(:admin_user))

    get admin_invitations_path

    assert_response :success
    assert_select "h3", /Recently Accepted/i
  end

  test "should show pending invitation count badge" do
    login_as(users(:admin_user))

    get admin_invitations_path

    assert_response :success
    assert_select "span.bg-yellow-100"
  end

  test "should display pending invitations table when invitations exist" do
    login_as(users(:admin_user))

    get admin_invitations_path

    assert_select "table"
    assert_select "th", text: "Email"
    assert_select "th", text: "Role"
    assert_select "th", text: "Sent"
  end

  # Create action - additional tests
  test "should create invitation with reviewer role" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "reviewer@example.com",
        name: "New Reviewer",
        role: "reviewer",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "reviewer@example.com")
    assert_equal "reviewer", new_user.role
  end

  test "should create invitation with admin role" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "admin@example.com",
        name: "New Admin",
        role: "admin",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "admin@example.com")
    assert_equal "admin", new_user.role
  end

  test "should ignore invalid role parameter" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "superadmin",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_equal "contributor", new_user.role
  end

  test "should set invited_by to current admin" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "contributor",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_equal users(:admin_user).id, new_user.invited_by_id
  end

  test "should generate random password for new invitation" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "contributor",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert new_user.password_digest.present?
  end

  test "should not create invitation with duplicate email" do
    login_as(users(:admin_user))
    existing_user = users(:admin_user)

    assert_no_difference("User.count") do
      post admin_invitations_path, params: {
        user: {
          email: existing_user.email,
          name: "Duplicate User",
          role: "contributor",
          primary_language: "en"
        }
      }
    end

    assert_response :unprocessable_entity
    assert_select "div.text-red-700", text: /already been taken/i
  end

  test "should create invitation even with blank name" do
    login_as(users(:admin_user))

    assert_difference("User.count", 1) do
      post admin_invitations_path, params: {
        user: {
          email: "newuser@example.com",
          name: "",
          role: "contributor",
          primary_language: "en"
        }
      }
    end

    assert_redirected_to admin_invitations_path
    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
  end

  test "should show success message with email after creating invitation" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "contributor",
        primary_language: "en"
      }
    }

    assert_redirected_to admin_invitations_path
    assert_match /Invitation sent to newuser@example\.com/, flash[:notice]
  end

  # Destroy action - additional tests
  test "should show success message after cancelling invitation" do
    login_as(users(:admin_user))

    delete admin_invitation_path(users(:pending_invitation))

    assert_redirected_to admin_invitations_path
    assert_equal "Invitation cancelled.", flash[:notice]
  end

  test "should delete user record when cancelling invitation" do
    login_as(users(:admin_user))
    pending_user = users(:pending_invitation)
    user_id = pending_user.id

    delete admin_invitation_path(pending_user)

    assert_nil User.find_by(id: user_id)
  end

  test "should not allow destroying user with entries" do
    login_as(users(:admin_user))
    user_with_entries = users(:contributor_user)

    # This user has accepted invitation and potentially has entries
    assert_no_difference("User.count") do
      delete admin_invitation_path(user_with_entries)
    end

    assert_redirected_to admin_invitations_path
    assert_equal "Cannot cancel an accepted invitation.", flash[:alert]
  end

  # Security tests
  test "should only permit email, name, primary_language, and role params" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "contributor",
        primary_language: "en",
        password: "hackedpassword123",
        invitation_token: "hacked_token",
        invitation_accepted_at: Time.current
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
    # Password should be randomly generated, not from params
    assert_not new_user.authenticate("hackedpassword123")
    # Invitation token should be generated by invite_by, not from params
    assert_not_equal "hacked_token", new_user.invitation_token
    # Invitation should not be pre-accepted
    assert_nil new_user.invitation_accepted_at
  end

  test "should require admin role for all actions" do
    login_as(users(:reviewer_user))

    # Index
    get admin_invitations_path
    assert_redirected_to root_path

    # New
    get new_admin_invitation_path
    assert_redirected_to root_path

    # Create
    post admin_invitations_path, params: { user: { email: "test@example.com", name: "Test" } }
    assert_redirected_to root_path

    # Resend
    put resend_admin_invitation_path(users(:pending_invitation))
    assert_redirected_to root_path

    # Destroy
    delete admin_invitation_path(users(:pending_invitation))
    assert_redirected_to root_path
  end

  # Edge cases
  test "should handle resending expired invitation" do
    login_as(users(:admin_user))
    pending_user = users(:pending_invitation)
    pending_user.update_columns(invitation_sent_at: 20.days.ago)

    put resend_admin_invitation_path(pending_user)

    assert_redirected_to admin_invitations_path
    pending_user.reload
    assert pending_user.invitation_sent_at > 1.hour.ago
  end

  test "should normalize email to lowercase when creating invitation" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "NewUser@Example.COM",
        name: "New User",
        role: "contributor",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
    assert_equal "newuser@example.com", new_user.email
  end

  test "should handle missing role parameter gracefully" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
    assert_equal "contributor", new_user.role
  end

  test "should handle blank role parameter" do
    login_as(users(:admin_user))

    post admin_invitations_path, params: {
      user: {
        email: "newuser@example.com",
        name: "New User",
        role: "",
        primary_language: "en"
      }
    }

    new_user = User.find_by(email: "newuser@example.com")
    assert_not_nil new_user
    assert_equal "contributor", new_user.role
  end
end
