require 'rails_helper'

RSpec.describe 'Users', type: :request do
  let(:remote_ip) { '203.0.113.10' }

  before do
    allow_any_instance_of(ActionDispatch::Request)
      .to receive(:remote_ip)
      .and_return(remote_ip)
  end

  def auth_headers(user)
    { 'X-Transfer-Code' => user.inheritance_code }
  end

  describe 'POST /users' do
    it 'creates guest user, IpAddress and UserIp, and returns code' do
      expect {
        post '/users'
      }.to change(User, :count).by(1)
        .and change(IpAddress, :count).by(1)
        .and change(UserIp, :count).by(1)

      expect(response).to have_http_status(:created)
      expect(json['code']).to be_present
      expect(json['user']['role']).to eq('guest')

      user = User.last
      ip_address = IpAddress.find_by(ip_address: IPAddr.new(remote_ip).hton)

      expect(user.role).to eq('guest')
      expect(ip_address).to be_present
      expect(UserIp.exists?(user:, ip_address:)).to eq(true)
    end

    it 'returns 403 and does not create user when current IP address is banned' do
      IpAddress.create!(
        ip_address: IPAddr.new(remote_ip).hton,
        banned_at: Time.current
      )

      expect {
        post '/users'
      }.not_to change(User, :count)

      expect(response).to have_http_status(:forbidden)
      expect(UserIp.count).to eq(0)
    end
  end

  describe 'POST /users/code/renew' do
    it 'returns 401 when not logged in' do
      post '/users/code/renew'

      expect(response).to have_http_status(:unauthorized)
    end

    it 'returns 403 when current user is banned' do
      user = create(:user, :banned)

      post '/users/code/renew', headers: auth_headers(user)

      expect(response).to have_http_status(:forbidden)
    end

    it 'returns 403 when current IP address is banned' do
      user = create(:user)

      IpAddress.create!(
        ip_address: IPAddr.new(remote_ip).hton,
        banned_at: Time.current
      )

      post '/users/code/renew', headers: auth_headers(user)

      expect(response).to have_http_status(:forbidden)
    end
  end

  describe 'PUT /users/:id' do
    let(:user) { create(:user, name: 'old-name', role: 'guest') }

    it 'returns 401 when current_user id mismatch' do
      other_user = create(:user)

      put "/users/#{user.id}",
          params: { name: 'new-name' },
          headers: auth_headers(other_user)

      expect(response).to have_http_status(:unauthorized)
    end

    it 'returns 422 when name is blank' do
      put "/users/#{user.id}",
          params: { name: '   ' },
          headers: auth_headers(user)

      expect(response).to have_http_status(:unprocessable_entity)
      expect(json.fetch('errors')).to include(
          'name' => ['名前は必須です．'])
    end

    it 'updates name and returns user slice' do
      put "/users/#{user.id}",
          params: { name: 'new-name' },
          headers: auth_headers(user)

      expect(response).to have_http_status(:ok)
      expect(json['id']).to eq(user.id)
      expect(json['name']).to eq('new-name')

      user.reload
      expect(user.name).to eq('new-name')
    end

    it 'returns 403 when current user is banned' do
      user.update!(banned_at: Time.current)

      put "/users/#{user.id}",
          params: { name: 'new-name' },
          headers: auth_headers(user)

      expect(response).to have_http_status(:forbidden)

      user.reload
      expect(user.name).to eq('old-name')
    end

    it 'returns 403 when current IP address is banned' do
      IpAddress.create!(
        ip_address: IPAddr.new(remote_ip).hton,
        banned_at: Time.current
      )

      put "/users/#{user.id}",
          params: { name: 'new-name' },
          headers: auth_headers(user)

      expect(response).to have_http_status(:forbidden)

      user.reload
      expect(user.name).to eq('old-name')
    end
  end

  describe 'POST /users/verify' do
    it 'returns valid:false when code not found' do
      post '/users/verify', params: { code: 'nope' }

      expect(response).to have_http_status(:ok)
      expect(json['valid']).to eq(false)
    end

    it 'returns 403 when current IP address is banned' do
      user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest')

      IpAddress.create!(
        ip_address: IPAddr.new(remote_ip).hton,
        banned_at: Time.current
      )

      expect {
        post '/users/verify', params: { code: user.inheritance_code }
      }.not_to change(UserIp, :count)

      expect(response).to have_http_status(:forbidden)
    end

    it 'returns 403 when verified user is banned' do
      user = create(
        :user,
        :banned,
        inheritance_code: SecureRandom.uuid,
        role: 'guest'
      )

      expect {
        post '/users/verify', params: { code: user.inheritance_code }
      }.not_to change(UserIp, :count)

      expect(response).to have_http_status(:forbidden)
    end

    it 'creates IpAddress and UserIp, and returns valid:true with user slice' do
      user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest')

      expect {
        post '/users/verify', params: { code: user.inheritance_code }
      }.to change(UserIp, :count).by(1)
        .and change(IpAddress, :count).by(1)

      expect(response).to have_http_status(:ok)
      expect(json['valid']).to eq(true)
      expect(json['user']['id']).to eq(user.id)
      expect(json['user']['inheritance_code']).to eq(user.inheritance_code)
      expect(json['user']['role']).to eq('guest')

      ip_address = IpAddress.find_by(ip_address: IPAddr.new(remote_ip).hton)
      expect(ip_address).to be_present
      expect(UserIp.exists?(user:, ip_address:)).to eq(true)
    end

    it 'is idempotent for same user and same IP address' do
      user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest')

      post '/users/verify', params: { code: user.inheritance_code }
      expect(response).to have_http_status(:ok)

      expect {
        post '/users/verify', params: { code: user.inheritance_code }
      }.not_to change(UserIp, :count)

      expect(response).to have_http_status(:ok)
      expect(json['valid']).to eq(true)
    end

    it 'creates another UserIp for same user and different IP address' do
      user = create(:user, inheritance_code: SecureRandom.uuid, role: 'guest')

      post '/users/verify', params: { code: user.inheritance_code }
      expect(response).to have_http_status(:ok)

      allow_any_instance_of(ActionDispatch::Request)
        .to receive(:remote_ip)
        .and_return('203.0.113.11')

      expect {
        post '/users/verify', params: { code: user.inheritance_code }
      }.to change(UserIp, :count).by(1)

      expect(response).to have_http_status(:ok)
      expect(json['valid']).to eq(true)
    end
  end

  describe 'GET /users/me' do
    it 'returns 404 when code not found' do
      get '/users/me', params: { code: 'nope' }

      expect(response).to have_http_status(:not_found)
    end

    it 'returns user slice when found' do
      user = create(:user, inheritance_code: SecureRandom.uuid, name: 'me', role: 'guest')

      get '/users/me', params: { code: user.inheritance_code }

      expect(response).to have_http_status(:ok)
      expect(json['id']).to eq(user.id)
      expect(json['name']).to eq('me')
      expect(json['inheritance_code']).to eq(user.inheritance_code)
      expect(json['role']).to eq('guest')
    end

    it 'returns 403 when current IP address is banned' do
      user = create(:user, inheritance_code: SecureRandom.uuid)

      IpAddress.create!(
        ip_address: IPAddr.new(remote_ip).hton,
        banned_at: Time.current
      )

      get '/users/me', params: { code: user.inheritance_code }

      expect(response).to have_http_status(:forbidden)
    end
  end
end
