# frozen_string_literal: true

# Main orchestrator service for parallel track generation
# Coordinates time chunking, job scheduling, and session management
class Tracks::ParallelGenerator
  include Tracks::Segmentation
  include Tracks::TrackBuilder

  attr_reader :user, :start_at, :end_at, :mode, :chunk_size

  def initialize(user, start_at: nil, end_at: nil, mode: :bulk, chunk_size: 1.day)
    @user = user
    @start_at = start_at
    @end_at = end_at
    @mode = mode.to_sym
    @chunk_size = chunk_size
  end

  def call
    clean_bulk_tracks if mode == :bulk

    # Generate time chunks
    time_chunks = generate_time_chunks
    return 0 if time_chunks.empty?

    # Create session for tracking progress
    session = create_generation_session(time_chunks.size)

    # Enqueue chunk processing jobs
    enqueue_chunk_jobs(session.session_id, time_chunks)

    # Enqueue boundary resolver job (with delay to let chunks complete)
    enqueue_boundary_resolver(session.session_id, time_chunks.size)

    Rails.logger.info "Started parallel track generation for user #{user.id} with #{time_chunks.size} chunks (session: #{session.session_id})"

    session
  end

  private

  def generate_time_chunks
    chunker = Tracks::TimeChunker.new(
      user,
      start_at: start_at,
      end_at: end_at,
      chunk_size: chunk_size
    )

    chunker.call
  end

  def create_generation_session(total_chunks)
    metadata = {
      mode: mode.to_s,
      chunk_size: humanize_duration(chunk_size),
      start_at: start_at&.iso8601,
      end_at: end_at&.iso8601,
      user_settings: {
        time_threshold_minutes: time_threshold_minutes,
        distance_threshold_meters: distance_threshold_meters
      }
    }

    session_manager = Tracks::SessionManager.create_for_user(user.id, metadata)
    session_manager.mark_started(total_chunks)
    session_manager
  end

  def enqueue_chunk_jobs(session_id, time_chunks)
    time_chunks.each do |chunk|
      Tracks::TimeChunkProcessorJob.perform_later(
        user.id,
        session_id,
        chunk
      )
    end
  end

  def enqueue_boundary_resolver(session_id, chunk_count)
    # Delay based on estimated processing time (30 seconds per chunk + buffer)
    estimated_delay = [chunk_count * 30.seconds, 5.minutes].max

    Tracks::BoundaryResolverJob.set(wait: estimated_delay).perform_later(
      user.id,
      session_id
    )
  end

  def clean_bulk_tracks
    if time_range_defined?
      user.tracks.where(
        '(start_at, end_at) OVERLAPS (?, ?)',
        start_at&.in_time_zone,
        end_at&.in_time_zone
      ).destroy_all
    else
      user.tracks.destroy_all
    end
  end

  def time_range_defined?
    start_at.present? || end_at.present?
  end

  def time_range
    return nil unless time_range_defined?

    start_time = start_at&.to_i
    end_time = end_at&.to_i

    if start_time && end_time
      Time.zone.at(start_time)..Time.zone.at(end_time)
    elsif start_time
      Time.zone.at(start_time)..
    elsif end_time
      ..Time.zone.at(end_time)
    end
  end

  def daily_time_range
    day = start_at&.to_date || Date.current
    day.beginning_of_day.to_i..day.end_of_day.to_i
  end

  def distance_threshold_meters
    @distance_threshold_meters ||= user.safe_settings.meters_between_routes.to_i
  end

  def time_threshold_minutes
    @time_threshold_minutes ||= user.safe_settings.minutes_between_routes.to_i
  end

  def humanize_duration(duration)
    case duration
    when 1.day then '1 day'
    when 1.hour then '1 hour'
    when 6.hours then '6 hours'
    when 12.hours then '12 hours'
    when 2.days then '2 days'
    when 1.week then '1 week'
    else
      # Convert seconds to readable format
      seconds = duration.to_i
      if seconds >= 86_400 # days
        days = seconds / 86_400
        "#{days} day#{'s' if days != 1}"
      elsif seconds >= 3600 # hours
        hours = seconds / 3600
        "#{hours} hour#{'s' if hours != 1}"
      elsif seconds >= 60 # minutes
        minutes = seconds / 60
        "#{minutes} minute#{'s' if minutes != 1}"
      else
        "#{seconds} second#{'s' if seconds != 1}"
      end
    end
  end
end
