# frozen_string_literal: true

class GoogleMaps::SemanticHistoryImporter
  include Imports::Broadcaster
  include Imports::FileLoader

  BATCH_SIZE = 1000
  attr_reader :import, :user_id, :file_path

  def initialize(import, user_id, file_path = nil)
    @import = import
    @user_id = user_id
    @file_path = file_path
    @current_index = 0
  end

  def call
    points_data.each_slice(BATCH_SIZE) do |batch|
      @current_index += batch.size
      process_batch(batch)
      broadcast_import_progress(import, @current_index)
    end
  end

  private

  def process_batch(batch)
    records = batch.map { |point_data| prepare_point_data(point_data) }

    # rubocop:disable Rails/SkipsModelValidations
    Point.upsert_all(
      records,
      unique_by: %i[lonlat timestamp user_id],
      returning: false,
      on_duplicate: :skip
    )
    # rubocop:enable Rails/SkipsModelValidations
  rescue StandardError => e
    create_notification("Failed to process location batch: #{e.message}")
  end

  def prepare_point_data(point_data)
    {
      lonlat: point_data[:lonlat],
      timestamp: point_data[:timestamp],
      accuracy: point_data[:accuracy],
      raw_data: point_data[:raw_data],
      topic: 'Google Maps Timeline Export',
      tracker_id: 'google-maps-timeline-export',
      import_id: import.id,
      user_id: user_id,
      created_at: Time.current,
      updated_at: Time.current
    }
  end

  def create_notification(message)
    Notification.create!(
      user_id: user_id,
      title: 'Google Maps Timeline Import Error',
      content: message,
      kind: :error
    )
  end

  def points_data
    json = load_json_data

    json['timelineObjects'].flat_map do |timeline_object|
      parse_timeline_object(timeline_object)
    end.compact
  end

  def parse_timeline_object(timeline_object)
    if timeline_object['activitySegment'].present?
      parse_activity_segment(timeline_object['activitySegment'])
    elsif timeline_object['placeVisit'].present?
      parse_place_visit(timeline_object['placeVisit'])
    end
  end

  def parse_activity_segment(activity)
    if activity['startLocation'].blank?
      parse_waypoints(activity)
    else
      build_point_from_location(
        longitude: activity['startLocation']['longitudeE7'],
        latitude: activity['startLocation']['latitudeE7'],
        timestamp: activity['duration']['startTimestamp'] || activity['duration']['startTimestampMs'],
        accuracy: activity.dig('startLocation', 'accuracyMetres'),
        raw_data: activity
      )
    end
  end

  def parse_waypoints(activity)
    return if activity['waypointPath'].blank?

    activity['waypointPath']['waypoints'].map do |waypoint|
      build_point_from_location(
        longitude: waypoint['lngE7'],
        latitude: waypoint['latE7'],
        timestamp: activity['duration']['startTimestamp'] || activity['duration']['startTimestampMs'],
        raw_data: activity
      )
    end
  end

  def parse_place_visit(place_visit)
    if place_visit.dig('location', 'latitudeE7').present? &&
       place_visit.dig('location', 'longitudeE7').present?
      build_point_from_location(
        longitude: place_visit['location']['longitudeE7'],
        latitude: place_visit['location']['latitudeE7'],
        timestamp: place_visit['duration']['startTimestamp'] || place_visit['duration']['startTimestampMs'],
        accuracy: place_visit.dig('location', 'accuracyMetres'),
        raw_data: place_visit
      )
    elsif (candidate = place_visit.dig('otherCandidateLocations', 0))
      parse_candidate_location(candidate, place_visit)
    end
  end

  def parse_candidate_location(candidate, place_visit)
    return unless candidate['latitudeE7'].present? && candidate['longitudeE7'].present?

    build_point_from_location(
      longitude: candidate['longitudeE7'],
      latitude: candidate['latitudeE7'],
      timestamp: place_visit['duration']['startTimestamp'] || place_visit['duration']['startTimestampMs'],
      accuracy: candidate['accuracyMetres'],
      raw_data: place_visit
    )
  end

  def build_point_from_location(longitude:, latitude:, timestamp:, raw_data:, accuracy: nil)
    {
      lonlat: "POINT(#{longitude.to_f / 10**7} #{latitude.to_f / 10**7})",
      timestamp: Timestamps.parse_timestamp(timestamp),
      accuracy: accuracy,
      raw_data: raw_data
    }
  end
end
