# frozen_string_literal: true

require 'rails_helper'

RSpec.describe LocationSearch::ResultAggregator do
  let(:service) { described_class.new }

  describe '#group_points_into_visits' do
    context 'with empty points array' do
      it 'returns empty array' do
        result = service.group_points_into_visits([])
        expect(result).to eq([])
      end
    end

    context 'with single point' do
      let(:single_point) do
        {
          id: 1,
          timestamp: 1711814700,
          coordinates: [52.5200, 13.4050],
          distance_meters: 45.5,
          accuracy: 10,
          date: '2024-03-20T18:45:00Z',
          city: 'Berlin',
          country: 'Germany',
          altitude: 100
        }
      end

      it 'creates a single visit' do
        result = service.group_points_into_visits([single_point])

        expect(result.length).to eq(1)
        visit = result.first
        expect(visit[:timestamp]).to eq(1711814700)
        expect(visit[:coordinates]).to eq([52.5200, 13.4050])
        expect(visit[:points_count]).to eq(1)
      end

      it 'estimates duration for single point visits' do
        result = service.group_points_into_visits([single_point])

        visit = result.first
        expect(visit[:duration_estimate]).to eq('~15 minutes')
        expect(visit[:visit_details][:duration_minutes]).to eq(15)
      end
    end

    context 'with consecutive points' do
      let(:consecutive_points) do
        [
          {
            id: 1,
            timestamp: 1711814700, # 18:45
            coordinates: [52.5200, 13.4050],
            distance_meters: 45.5,
            accuracy: 10,
            date: '2024-03-20T18:45:00Z',
            city: 'Berlin',
            country: 'Germany'
          },
          {
            id: 2,
            timestamp: 1711816500, # 19:15 (30 minutes later)
            coordinates: [52.5201, 13.4051],
            distance_meters: 48.2,
            accuracy: 8,
            date: '2024-03-20T19:15:00Z',
            city: 'Berlin',
            country: 'Germany'
          },
          {
            id: 3,
            timestamp: 1711817400, # 19:30 (15 minutes later)
            coordinates: [52.5199, 13.4049],
            distance_meters: 42.1,
            accuracy: 12,
            date: '2024-03-20T19:30:00Z',
            city: 'Berlin',
            country: 'Germany'
          }
        ]
      end

      it 'groups consecutive points into single visit' do
        result = service.group_points_into_visits(consecutive_points)

        expect(result.length).to eq(1)
        visit = result.first
        expect(visit[:points_count]).to eq(3)
      end

      it 'calculates visit duration from start to end' do
        result = service.group_points_into_visits(consecutive_points)

        visit = result.first
        expect(visit[:duration_estimate]).to eq('~45 minutes')
        expect(visit[:visit_details][:duration_minutes]).to eq(45)
      end

      it 'uses most accurate point coordinates' do
        result = service.group_points_into_visits(consecutive_points)

        visit = result.first
        # Point with accuracy 8 should be selected
        expect(visit[:coordinates]).to eq([52.5201, 13.4051])
        expect(visit[:accuracy_meters]).to eq(8)
      end

      it 'calculates average distance' do
        result = service.group_points_into_visits(consecutive_points)

        visit = result.first
        expected_avg = (45.5 + 48.2 + 42.1) / 3
        expect(visit[:distance_meters]).to eq(expected_avg.round(2))
      end

      it 'sets correct start and end times' do
        result = service.group_points_into_visits(consecutive_points)

        visit = result.first
        expect(visit[:visit_details][:start_time]).to eq('2024-03-20T18:45:00Z')
        expect(visit[:visit_details][:end_time]).to eq('2024-03-20T19:30:00Z')
      end
    end

    context 'with separate visits (time gaps)' do
      let(:separate_visits_points) do
        [
          {
            id: 1,
            timestamp: 1711814700, # 18:45
            coordinates: [52.5200, 13.4050],
            distance_meters: 45.5,
            accuracy: 10,
            date: '2024-03-20T18:45:00Z',
            city: 'Berlin',
            country: 'Germany'
          },
          {
            id: 2,
            timestamp: 1711816500, # 19:15 (30 minutes later - within threshold)
            coordinates: [52.5201, 13.4051],
            distance_meters: 48.2,
            accuracy: 8,
            date: '2024-03-20T19:15:00Z',
            city: 'Berlin',
            country: 'Germany'
          },
          {
            id: 3,
            timestamp: 1711820100, # 20:15 (60 minutes after last point - exceeds threshold)
            coordinates: [52.5199, 13.4049],
            distance_meters: 42.1,
            accuracy: 12,
            date: '2024-03-20T20:15:00Z',
            city: 'Berlin',
            country: 'Germany'
          }
        ]
      end

      it 'creates separate visits when time gap exceeds threshold' do
        result = service.group_points_into_visits(separate_visits_points)

        expect(result.length).to eq(2)
        expect(result.first[:points_count]).to eq(1) # Most recent visit (20:15)
        expect(result.last[:points_count]).to eq(2)  # Earlier visit (18:45-19:15)
      end

      it 'orders visits by timestamp descending (most recent first)' do
        result = service.group_points_into_visits(separate_visits_points)

        expect(result.first[:timestamp]).to be > result.last[:timestamp]
      end
    end

    context 'with duration formatting' do
      let(:points_with_various_durations) do
        # Helper to create points with time differences
        base_time = 1711814700

        [
          # Short visit (25 minutes) - 2 points 25 minutes apart
          { id: 1, timestamp: base_time, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T18:45:00Z' },
          { id: 2, timestamp: base_time + 25 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T19:10:00Z' },
          
          # Long visit (2 hours 15 minutes) - points every 15 minutes to stay within 30min threshold
          { id: 3, timestamp: base_time + 70 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T19:55:00Z' },
          { id: 4, timestamp: base_time + 85 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T20:10:00Z' },
          { id: 5, timestamp: base_time + 100 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T20:25:00Z' },
          { id: 6, timestamp: base_time + 115 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T20:40:00Z' },
          { id: 7, timestamp: base_time + 130 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T20:55:00Z' },
          { id: 8, timestamp: base_time + 145 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T21:10:00Z' },
          { id: 9, timestamp: base_time + 160 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T21:25:00Z' },
          { id: 10, timestamp: base_time + 175 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T21:40:00Z' },
          { id: 11, timestamp: base_time + 190 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T21:55:00Z' },
          { id: 12, timestamp: base_time + 205 * 60, accuracy: 10, coordinates: [52.5300, 13.4100], distance_meters: 30, date: '2024-03-20T22:10:00Z' }
        ]
      end

      it 'formats duration correctly for minutes only' do
        short_visit_points = points_with_various_durations.take(2)
        result = service.group_points_into_visits(short_visit_points)

        expect(result.first[:duration_estimate]).to eq('~25 minutes')
      end

      it 'formats duration correctly for hours and minutes' do
        long_visit_points = points_with_various_durations.drop(2)
        result = service.group_points_into_visits(long_visit_points)

        expect(result.first[:duration_estimate]).to eq('~2 hours 15 minutes')
      end

      it 'formats duration correctly for hours only' do
        # Create points within threshold but exactly 2 hours apart from first to last
        exact_hour_points = [
          { id: 1, timestamp: 1711814700, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T18:45:00Z' },
          { id: 2, timestamp: 1711814700 + 25 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T19:10:00Z' },
          { id: 3, timestamp: 1711814700 + 50 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T19:35:00Z' },
          { id: 4, timestamp: 1711814700 + 75 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T20:00:00Z' },
          { id: 5, timestamp: 1711814700 + 100 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T20:25:00Z' },
          { id: 6, timestamp: 1711814700 + 120 * 60, accuracy: 10, coordinates: [52.5200, 13.4050], distance_meters: 50, date: '2024-03-20T20:45:00Z' }
        ]
        
        result = service.group_points_into_visits(exact_hour_points)

        expect(result.first[:duration_estimate]).to eq('~2 hours')
      end
    end

    context 'with altitude data' do
      let(:points_with_altitude) do
        [
          {
            id: 1, timestamp: 1711814700, coordinates: [52.5200, 13.4050],
            accuracy: 10, distance_meters: 50, altitude: 100,
            date: '2024-03-20T18:45:00Z'
          },
          {
            id: 2, timestamp: 1711815600, coordinates: [52.5201, 13.4051],
            accuracy: 10, distance_meters: 50, altitude: 105,
            date: '2024-03-20T19:00:00Z'
          },
          {
            id: 3, timestamp: 1711816500, coordinates: [52.5199, 13.4049],
            accuracy: 10, distance_meters: 50, altitude: 95,
            date: '2024-03-20T19:15:00Z'
          }
        ]
      end

      it 'includes altitude range in visit details' do
        result = service.group_points_into_visits(points_with_altitude)

        visit = result.first
        expect(visit[:visit_details][:altitude_range]).to eq('95m - 105m')
      end

      context 'with same altitude for all points' do
        before do
          points_with_altitude.each { |p| p[:altitude] = 100 }
        end

        it 'shows single altitude value' do
          result = service.group_points_into_visits(points_with_altitude)

          visit = result.first
          expect(visit[:visit_details][:altitude_range]).to eq('100m')
        end
      end

      context 'with missing altitude data' do
        before do
          points_with_altitude.each { |p| p.delete(:altitude) }
        end

        it 'handles missing altitude gracefully' do
          result = service.group_points_into_visits(points_with_altitude)

          visit = result.first
          expect(visit[:visit_details][:altitude_range]).to be_nil
        end
      end
    end

    context 'with unordered points' do
      let(:unordered_points) do
        [
          { id: 3, timestamp: 1711817400, coordinates: [52.5199, 13.4049], accuracy: 10, distance_meters: 50, date: '2024-03-20T19:30:00Z' },
          { id: 1, timestamp: 1711814700, coordinates: [52.5200, 13.4050], accuracy: 10, distance_meters: 50, date: '2024-03-20T18:45:00Z' },
          { id: 2, timestamp: 1711816500, coordinates: [52.5201, 13.4051], accuracy: 10, distance_meters: 50, date: '2024-03-20T19:15:00Z' }
        ]
      end

      it 'handles unordered input correctly' do
        result = service.group_points_into_visits(unordered_points)

        visit = result.first
        expect(visit[:visit_details][:start_time]).to eq('2024-03-20T18:45:00Z')
        expect(visit[:visit_details][:end_time]).to eq('2024-03-20T19:30:00Z')
      end
    end
  end
end