# encoding: UTF-8
# frozen_string_literal: true
# Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

require_relative 'base_action'

module GoodData
  module LCM2
    class UpdateMetricFormats < BaseAction
      DESCRIPTION = 'Localize Metric Formats'

      PARAMS = define_params(self) do
        description 'Synchronization Info'
        param :synchronize, array_of(instance_of(Type::SynchronizationInfoType)), required: true, generated: true

        description 'Client Used for Connecting to GD'
        param :gdc_gd_client, instance_of(Type::GdClientType), required: true

        description 'Organization Name'
        param :organization, instance_of(Type::StringType), required: false

        description 'DataProduct to manage'
        param :data_product, instance_of(Type::GDDataProductType), required: false

        description 'Logger'
        param :gdc_logger, instance_of(Type::GdLogger), required: true

        description 'ADS Client'
        param :ads_client, instance_of(Type::AdsClientType), required: false

        description 'Input Source'
        param :input_source, instance_of(Type::HashType), required: false

        description 'Localization query'
        param :localization_query, instance_of(Type::StringType), required: false

        description 'Abort on error'
        param :abort_on_error, instance_of(Type::StringType), required: false

        description 'Collect synced status'
        param :collect_synced_status, instance_of(Type::BooleanType), required: false

        description 'Sync failed list'
        param :sync_failed_list, instance_of(Type::HashType), required: false
      end

      RESULT_HEADER = %i[action ok_clients error_clients]

      class << self
        def load_metric_data(params)
          collect_synced_status = collect_synced_status(params)

          if params&.dig(:input_source, :metric_format) && params[:input_source][:metric_format].present?
            metric_input_source = validate_input_source(params[:input_source], collect_synced_status)
            return nil unless metric_input_source
          else
            return nil
          end

          metric_data_source = GoodData::Helpers::DataSource.new(metric_input_source)
          begin
            temp_csv = without_check(PARAMS, params) do
              File.open(metric_data_source.realize(params), 'r:UTF-8')
            end
          rescue StandardError => e
            GoodData.logger.warn("Unable to get metric input source, skip updating metric formats. Error: #{e.message} - #{e}")
            return nil
          end

          metrics_hash = GoodData::Helpers::Csv.read_as_hash temp_csv
          return nil if metrics_hash.empty?

          expected_keys = %w[tag client_id format]
          unless expected_keys.map(&:to_sym).all? { |s| metrics_hash.first.key? s }
            GoodData.logger.warn("The input metric data is incorrect, expecting the following fields: #{expected_keys}")
            return nil
          end
          metrics_hash
        end

        def validate_input_source(input_source, continue_on_error)
          type = input_source[:type] if input_source&.dig(:type)
          metric_format = input_source[:metric_format]
          if type.blank?
            raise "Incorrect configuration: 'type' of 'input_source' is required" unless continue_on_error

            return nil
          end

          modified_input_source = input_source
          case type
          when 'ads', 'redshift', 'snowflake', 'bigquery', 'postgresql', 'mssql', 'mysql'
            if metric_format[:query].blank?
              GoodData.logger.warn("The metric input_source '#{type}' is missing property 'query'")
              return nil
            end

            modified_input_source[:query] = metric_format[:query]
            return modified_input_source
          when 's3'
            if metric_format[:file].blank?
              GoodData.logger.warn("The metric input_source '#{type}' is missing property 'file'")
              return nil
            end

            if modified_input_source.key?(:key)
              modified_input_source[:key] = metric_format[:file]
            else
              modified_input_source[:file] = metric_format[:file]
            end
            return modified_input_source
          when 'blobStorage'
            if metric_format[:file].blank?
              GoodData.logger.warn("The metric input_source '#{type}' is missing property 'file'")
              return nil
            end

            modified_input_source[:file] = metric_format[:file]
            return modified_input_source
          when 'staging'
            if metric_format[:file].blank?
              GoodData.logger.warn("The metric input_source '#{type}' is missing property 'file'")
              return nil
            end

            modified_input_source[:path] = metric_format[:file]
            return modified_input_source
          when 'web'
            if metric_format[:url].blank?
              GoodData.logger.warn("The metric input_source '#{type}' is missing property 'url'")
              return nil
            end

            modified_input_source[:url] = metric_format[:url]
            return modified_input_source
          else
            return nil
          end
        end

        def get_clients_metrics(metric_data)
          return {} if metric_data.nil?

          metric_groups = {}
          clients = metric_data.map { |row| row[:client_id] }.uniq
          clients.each do |client|
            next if client.blank?

            formats = {}
            metric_data.select { |row| row[:client_id] == client && row[:tag].present? && row[:format].present? }.each { |row| formats[row[:tag]] = row[:format] }
            metric_groups[client.to_s] ||= formats
          end
          metric_groups
        end

        def call(params)
          data = load_metric_data(params)
          result = []
          return result if data.nil?

          metric_group = get_clients_metrics(data)
          return result if metric_group.empty?

          GoodData.logger.debug("Clients have metrics which will be modified: #{metric_group.keys}")
          updated_clients = params.synchronize.map { |segment| segment.to.map { |client| client[:client_id] } }.flatten.uniq
          GoodData.logger.debug("Updating clients: #{updated_clients}")
          data_product = params.data_product
          data_product_clients = data_product.clients
          number_client_ok = 0
          number_client_error = 0
          collect_synced_status = collect_synced_status(params)
          metric_group.each do |client_id, formats|
            next if !updated_clients.include?(client_id) || (collect_synced_status && sync_failed_client(client_id, params))

            client = data_product_clients.find { |c| c.id == client_id }
            begin
              GoodData.logger.info("Start updating metric format for client: '#{client_id}'")
              metrics = client.project.metrics.to_a
              formats.each do |tag, format|
                next if tag.blank? || format.blank?

                metrics_to_be_updated = metrics.select { |metric| metric.tags.include?(tag) }
                metrics_to_be_updated.each do |metric|
                  metric.format = format
                  metric.save
                end
              end
              number_client_ok += 1
              GoodData.logger.info("Finished updating metric format for client: '#{client_id}'")
            rescue StandardError => e
              number_client_error += 1
              GoodData.logger.warn("Failed to update metric format for client: '#{client_id}'. Error: #{e.message} - #{e}")
            end
          end
          [{ :action => 'Update metric format', :ok_clients => number_client_ok, :error_clients => number_client_error }]
        end
      end
    end
  end
end
