# frozen_string_literal: true

class Visits::Group
  def initialize(time_threshold_minutes: 30, merge_threshold_minutes: 15)
    @time_threshold_minutes = time_threshold_minutes
    @merge_threshold_minutes = merge_threshold_minutes
    @visits = []
    @current_visit = nil
  end

  def call(points)
    process_points(points.sort_by(&:timestamp))
    finalize_current_visit
    merge_visits
    convert_to_hash
  end

  private

  def process_points(sorted_points)
    sorted_points.each { process_point(_1) }
  end

  def process_point(point)
    point_time = point.timestamp
    log_point_processing(point_time)

    @current_visit.nil? ? start_new_visit(point_time, point) : handle_existing_visit(point_time, point)
  end

  def start_new_visit(point_time, point)
    log_new_visit(point_time)

    @current_visit = VisitDraft.new(point_time)
    @current_visit.add_point(point)
  end

  def handle_existing_visit(point_time, point)
    time_difference = calculate_time_difference(point_time)
    log_time_difference(time_difference)

    if time_difference <= @time_threshold_minutes
      @current_visit.add_point(point)
    else
      finalize_current_visit
      start_new_visit(point_time, point)
    end
  end

  def calculate_time_difference(point_time)
    (point_time - @current_visit.end_time) / 60.0
  end

  def finalize_current_visit
    return if @current_visit.nil?

    if @current_visit.valid?
      log_valid_visit
      @visits << @current_visit
    else
      log_invalid_visit
    end

    @current_visit = nil
  end

  def merge_visits
    merged_visits = []
    previous_visit = nil

    @visits.each do |visit|
      if previous_visit.nil?
        previous_visit = visit
      else
        time_difference = (visit.start_time - previous_visit.end_time) / 60.0

        if time_difference <= @merge_threshold_minutes
          merge_visit(previous_visit, visit)
        else
          merged_visits << previous_visit
          previous_visit = visit
        end
      end
    end

    merged_visits << previous_visit if previous_visit
    @visits = merged_visits.sort_by(&:start_time)
  end

  def merge_visit(previous_visit, current_visit)
    previous_visit.points.concat(current_visit.points)
    previous_visit.end_time = current_visit.end_time
  end

  def convert_to_hash
    @visits.each_with_object({}) do |visit, hash|
      hash[format_time_range(visit)] = visit.points
    end
  end

  def format_time_range(visit)
    start_time = format_time(visit.start_time)
    end_time = format_time(visit.end_time)
    "#{start_time} - #{end_time}"
  end

  def format_time(timestamp)
    Time.zone.at(timestamp).strftime('%Y-%m-%d %H:%M')
  end

  def log_point_processing(point_time)
    Rails.logger.info("Processing point at #{format_time(point_time)}")
  end

  def log_new_visit(point_time)
    Rails.logger.info("Starting new visit at #{format_time(point_time)}")
  end

  def log_time_difference(time_difference)
    Rails.logger.info("Time difference: #{time_difference.round} minutes")
  end

  def log_valid_visit
    Rails.logger.info("Ending visit from #{format_time(@current_visit.start_time)} to #{format_time(@current_visit.end_time)}, duration: #{@current_visit.duration_in_minutes} minutes, points: #{@current_visit.points.size}") # rubocop:disable Layout/LineLength
  end

  def log_invalid_visit
    Rails.logger.info("Discarding visit from #{format_time(@current_visit.start_time)} to #{format_time(@current_visit.end_time)} (invalid, points: #{@current_visit.points.size}, duration: #{@current_visit.duration_in_minutes} minutes)") # rubocop:disable Layout/LineLength
  end
end
