# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Visits::Create do
  let(:user) { create(:user) }
  let(:valid_params) do
    {
      name: 'Test Visit',
      latitude: 52.52,
      longitude: 13.405,
      started_at: '2023-12-01T10:00:00Z',
      ended_at: '2023-12-01T12:00:00Z'
    }
  end

  describe '#call' do
    context 'when all parameters are valid' do
      subject(:service) { described_class.new(user, valid_params) }

      it 'creates a visit successfully' do
        expect { service.call }.to change { user.visits.count }.by(1)
        expect(service.call).to be_truthy
        expect(service.visit).to be_persisted
      end

      it 'creates a visit with correct attributes' do
        service.call
        visit = service.visit

        expect(visit.name).to eq('Test Visit')
        expect(visit.user).to eq(user)
        expect(visit.status).to eq('confirmed')
        expect(visit.started_at).to eq(DateTime.parse('2023-12-01T10:00:00Z'))
        expect(visit.ended_at).to eq(DateTime.parse('2023-12-01T12:00:00Z'))
        expect(visit.duration).to eq(120) # 2 hours in minutes
      end

      it 'creates a place with correct coordinates' do
        service.call
        place = service.visit.place

        expect(place).to be_persisted
        expect(place.name).to eq('Test Visit')
        expect(place.latitude).to eq(52.52)
        expect(place.longitude).to eq(13.405)
        expect(place.source).to eq('manual')
      end
    end

    context 'when reusing existing place' do
      let!(:existing_place) do
        create(:place,
               latitude: 52.52,
               longitude: 13.405,
               lonlat: 'POINT(13.405 52.52)')
      end
      let!(:existing_visit) { create(:visit, user: user, place: existing_place) }

      subject(:service) { described_class.new(user, valid_params) }

      it 'reuses the existing place' do
        expect { service.call }.not_to change { Place.count }
        expect(service.visit.place).to eq(existing_place)
      end

      it 'creates a new visit with the existing place' do
        expect { service.call }.to change { user.visits.count }.by(1)
        expect(service.visit.place).to eq(existing_place)
      end
    end

    context 'when place creation fails' do
      subject(:service) { described_class.new(user, valid_params) }

      before do
        allow(Place).to receive(:create!).and_raise(ActiveRecord::RecordInvalid.new(Place.new))
      end

      it 'returns false' do
        expect(service.call).to be(false)
      end

      it 'calls ExceptionReporter' do
        expect(ExceptionReporter).to receive(:call)

        service.call
      end

      it 'does not create a visit' do
        expect { service.call }.not_to change { Visit.count }
      end
    end

    context 'when visit creation fails' do
      subject(:service) { described_class.new(user, valid_params) }

      before do
        allow_any_instance_of(User).to receive_message_chain(:visits, :create!).and_raise(ActiveRecord::RecordInvalid.new(Visit.new))
      end

      it 'returns false' do
        expect(service.call).to be(false)
      end

      it 'calls ExceptionReporter' do
        expect(ExceptionReporter).to receive(:call)

        service.call
      end
    end

    context 'edge cases' do
      context 'when name is not provided but defaults are used' do
        let(:params) { valid_params.merge(name: '') }
        subject(:service) { described_class.new(user, params) }

        it 'returns false due to validation' do
          expect(service.call).to be(false)
        end
      end

      context 'when coordinates are strings' do
        let(:params) do
          valid_params.merge(
            latitude: '52.52',
            longitude: '13.405'
          )
        end
        subject(:service) { described_class.new(user, params) }

        it 'converts them to floats and creates visit successfully' do
          expect(service.call).to be_truthy
          place = service.visit.place
          expect(place.latitude).to eq(52.52)
          expect(place.longitude).to eq(13.405)
        end
      end

      context 'when visit duration is very short' do
        let(:params) do
          valid_params.merge(
            started_at: '2023-12-01T12:00:00Z',
            ended_at: '2023-12-01T12:01:00Z' # 1 minute
          )
        end
        subject(:service) { described_class.new(user, params) }

        it 'creates visit with correct duration' do
          service.call
          expect(service.visit.duration).to eq(1)
        end
      end

      context 'when visit duration is very long' do
        let(:params) do
          valid_params.merge(
            started_at: '2023-12-01T08:00:00Z',
            ended_at: '2023-12-02T20:00:00Z' # 36 hours
          )
        end
        subject(:service) { described_class.new(user, params) }

        it 'creates visit with correct duration' do
          service.call
          expect(service.visit.duration).to eq(36 * 60) # 36 hours in minutes
        end
      end

      context 'when datetime-local input is provided without timezone' do
        let(:params) do
          valid_params.merge(
            started_at: '2023-12-01T19:54',
            ended_at: '2023-12-01T20:54'
          )
        end
        subject(:service) { described_class.new(user, params) }

        it 'parses the datetime in the application timezone' do
          service.call
          visit = service.visit

          expect(visit.started_at.hour).to eq(19)
          expect(visit.started_at.min).to eq(54)
          expect(visit.ended_at.hour).to eq(20)
          expect(visit.ended_at.min).to eq(54)
        end

        it 'calculates correct duration' do
          service.call
          expect(service.visit.duration).to eq(60) # 1 hour in minutes
        end
      end
    end
  end
end
