require 'zip'
require_dependency 'csv'
require_dependency 'stats'

class WeeklyProviderStatsWorker
  include Sidekiq::Worker
  include ActiveSupport::Benchmarkable
  sidekiq_options queue: :low

  delegate :logger, to: Rails

  attr_reader :definition_fields
  attr_accessor :providers

  def initialize
    @definition_fields = Account.master.fields_definitions
    @providers = Account.providers
  end

  def perform
    email.deliver_now
  end

  def attachment
    zip('provider_stats.csv' => generate_csv).read
  end

  def email
    ThreeScaleMailer.provider_weekly_stats('provider_stats.csv.zip' => attachment)
  end

  def zip(files)
    buffer = ::Zip::OutputStream.write_buffer do |zip|
      files.each do |file,content|
        zip.put_next_entry file
        zip.print content
      end
    end

    buffer.close_write
    buffer.rewind
    buffer
  end

  def headers
    headers = [
        "id", "org_name", "application plan", "email", "first_name last_name", "phone",
        "domain", "signup date", "is_custom", "days since creation of provider account",
        "days since the last buyer account was created", "days since last traffic was seen",
        "days since any of the CMS tables were modified", "days since last sign-in from provider",
        "days since last sign-in from buyers", "number of buyer accounts"
    ]

    headers.concat definition_fields.map(&:label)

    headers
  end

  def generate_csv
    CSV.generate do |csv|
      csv << headers

      @providers.find_each(batch_size: 500) do |p|
        csv << benchmark("Generating report for provider #{p.org_name} (id: #{p.id})") do
          row_for_provider(p)
        end
      end
    end
  end

  def row_for_provider(provider)
    admin = provider.admins.first
    row = []
    row << provider.id
    row << provider.org_name
    row << provider.bought_plan.name
    row << admin.try!(:email)
    row << admin.try!(:full_name)
    row << provider.billing_address_phone
    row << provider.domain
    row << provider.created_at
    row << provider.domain.try!(:ends_with?, '.3scale.net') ? '0' : '1'
    row << days_since(provider.created_at)

    ## do the 3 rows that have to do with buyer_accounts in one traversal
    buyer_accounts_size = 0
    last_login_total = nil
    last_buyer_account = nil

    Account.unscoped do
      buyers = provider.buyer_accounts
      buyer_accounts_size = buyers.size

      with_sessions = buyers
        .includes(users: :user_sessions)
        .order('user_sessions.accessed_at')
        .having('COUNT(user_sessions.id) > 0')

      last_buyer_account = with_sessions.order('MAX(user_sessions.accessed_at) DESC').select(:id).first
      last_login_total = with_sessions.maximum('user_sessions.accessed_at')
    end

    row << days_since((last_buyer_account || provider).created_at)
    row << days_since(TrafficService.build(provider).last_traffic_date)
    row << days_since(provider.templates.maximum(:updated_at))
    row << days_since(last_admin_access_of(provider))
    #too much to just get the newer date
    #row << (days_since( p.buyer_accounts.map{|a| a.users }.flatten.reject{|u| u.last_login_at.nil? }.sort_by{|user| user.last_login_at }.last.try(:last_login_at)) || -1)
    row << days_since(last_login_total)
    row << buyer_accounts_size
    definition_fields.each { |field| row << provider.extra_fields[field.name] }
    row
  rescue Account::BuyerMethods::ApplicationNotFound => e
    Rails.logger.error "[#{self.class}] Error #{e} when generating stats for provider #{provider.id} (#{provider.org_name})"
    []
  end

  def days_since(time)
    return -1 if time.nil?
    ((Time.now - time.to_time) / 60 / 60 / 24).to_i
  end

  def last_admin_access_of(account)
    account
      .admins
      .joins(:user_sessions)
      .maximum('user_sessions.accessed_at')
  end
end
