# frozen_string_literal: true

# Estadísticas del sitio
class StatsController < ApplicationController
  include Pundit
  include ActionView::Helpers::DateHelper

  before_action :authenticate_usuarie!
  before_action :authorize_stats

  breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
  breadcrumb 'sites.index', :sites_path, match: :exact
  breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact

  EXTRA_OPTIONS = {
    builds: {},
    space_used: { bytes: true },
    build_time: {}
  }.freeze

  # XXX: Permitir a Chart.js inyectar su propio CSS
  content_security_policy only: :index do |policy|
    policy.style_src :self, :unsafe_inline
    policy.script_src :self, :unsafe_inline
  end

  # Parámetros por defecto
  #
  # @return [Hash]
  def default_url_options
    { interval: 'day', period_start: Date.today.beginning_of_year, period_end: Date.today }
  end

  def index
    breadcrumb I18n.t('stats.index.title'), ''

    params.with_defaults! default_url_options

    @chart_params = {
      interval: interval,
      period_start: params[:period_start],
      period_end: params[:period_end]
    }

    hostnames
    last_stat
    chart_options
    normalized_urls

    expires_in = Time.now.try(:"end_of_#{Stat.default_interval}") - Time.now
    @columns = {}

    Stat::COLUMNS.each do |column|
      @columns[column] =
        Rails.cache.fetch("stats/#{column}/#{site.id}", expires_in: expires_in) do
          rollup_scope.where(interval: interval, name: "host|#{column}")
                      .where_dimensions(host: hostnames)
                      .group("dimensions->>'#{column}'")
                      .order('sum(value) desc')
                      .sum(:value)
                      .transform_values(&:to_i)
                      .transform_values do |v|
                        v * nodes
                      end
        end
    end
  end

  # Genera un gráfico de visitas por dominio asociado a este sitio
  def host
    return unless stale? [last_stat, hostnames, interval, period]

    stats = rollup_scope.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series|
      series.each do |serie|
        serie[:name] = serie.dig(:dimensions, 'host')
        serie[:data].transform_values! do |value|
          value * nodes
        end
      end
    end

    render json: stats
  end

  def resources
    return unless stale? [last_stat, interval, resource, period]

    options = { interval: interval, dimensions: { site_id: site.id } }

    render json: rollup_scope.series(resource, **options)
  end

  def uris
    return unless stale? [last_stat, hostnames, interval, normalized_urls, period]

    options = { host: hostnames, uri: normalized_paths }
    # XXX: where_dimensions es más corto pero no aprovecha los índices
    # de Rollup
    stats = rollup_scope.where_dimensions(**options).multi_series('host|uri', interval: interval).tap do |series|
      series.each do |serie|
        serie[:name] = serie[:dimensions].slice('host', 'uri').values.join.sub('/index.html', '/')
        serie[:data].transform_values! do |value|
          value * nodes
        end
      end
    end

    render json: stats
  end

  private

  def rollup_scope
    Rollup.where(time: period)
  end

  def last_stat
    @last_stat ||= site.stats.last
  end

  def authorize_stats
    authorize SiteStat.new(site)
  end

  # TODO: Eliminar cuando mergeemos referer-origin
  def hostnames
    @hostnames ||= site.hostnames
  end

  # Normalizar las URLs
  #
  # @return [Array]
  def normalized_urls
    @normalized_urls ||=
      begin
        urls = params[:urls].is_a?(Array) ? params[:urls] : params[:urls]&.split("\n")
        urls = urls&.map(&:strip)&.select(&:present?)&.select do |uri|
          uri.start_with? 'https://'
        end

        urls ||= [site.url]

        urls.map do |u|
          # XXX: Eliminar al deployear
          # @see {https://0xacab.org/sutty/containers/nginx/-/merge_requests/1}
          next u unless u.end_with? '/'

          "#{u}index.html"
        end.uniq
      end
  end

  def normalized_paths
    @normalized_paths ||= normalized_urls.map do |u|
      "/#{u.split('/', 4).last}"
    end.map do |u|
      URI.decode_www_form_component u
    end
  end

  # Opciones por defecto para los gráficos.
  #
  # La invitación a volver dentro de X tiempo es para dar un estimado de
  # cuándo habrá información disponible, porque Rollup genera intervalos
  # completos (¿aunque dice que no?)
  #
  # La diferencia se calcula sumando el intervalo a la hora de última
  # toma de estadísticas y restando el tiempo que pasó desde ese
  # momento.
  def chart_options
    time = (last_stat&.created_at || Time.now) + 1.try(interval)
    please_return_at = { please_return_at: distance_of_time_in_words(Time.now, time) }

    @chart_options ||= {
      locale: I18n.locale,
      empty: I18n.t('stats.index.empty', **please_return_at),
      loading: I18n.t('stats.index.loading'),
      html: %(<div id="%{id}" class="d-flex align-items-center justify-content-center" style="height: %{height}; width: %{width};">%{loading}</div>)
    }
  end

  # Obtiene y valida los intervalos
  #
  # @return [Symbol]
  def interval
    @interval ||= begin
      i = params[:interval]&.to_sym
      Stat::INTERVALS.include?(i) ? i : Stat::INTERVALS.first
    end
  end

  # @return [Symbol]
  def resource
    @resource ||= begin
      r = params[:resource].to_sym
      Stat::RESOURCES.include?(r) ? r : Stat::RESOURCES.first
    end
  end

  # Obtiene la cantidad de nodos de Sutty, para poder calcular la
  # cantidad de visitas.
  #
  # Como repartimos las visitas por nodo rotando las IPs en el
  # nameserver y los resolvedores de DNS eligen un nameserver
  # aleatoriamente, la cantidad de visitas se reparte
  # equitativamente.
  #
  # XXX: Remover cuando podamos centralizar los AccessLog
  #
  # @return [Integer]
  def nodes
    @nodes ||= ENV.fetch('NODES', 1).to_i
  end

  def period
    @period ||= begin
      p = params.permit(:period_start, :period_end)
      p[:period_start]..p[:period_end]
    end
  end

  def site
    @site ||= find_site
  end
end