# The MIT License (MIT)

# Copyright (c) 2023 Mike DeAngelo Google, Inc.

# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# frozen_string_literal: true

require_relative '../../../gzr'
require_relative '../../command'
require_relative '../../modules/dashboard'
require_relative '../../modules/look'
require_relative '../../modules/user'
require_relative '../../modules/plan'
require_relative '../../modules/filehelper'

module Gzr
  module Commands
    class Dashboard
      class Import < Gzr::Command
        include Gzr::Dashboard
        include Gzr::Look
        include Gzr::User
        include Gzr::Plan
        include Gzr::FileHelper
        def initialize(file, dest_folder_id, options)
          super()
          @file = file
          @dest_folder_id = dest_folder_id
          @options = options
        end

        def execute(input: $stdin, output: $stdout)
          say_warning("options: #{@options.inspect}", output: output) if @options[:debug]
          with_session do

            @me ||= query_me("id")

            read_file(@file) do |data|

              if data[:deleted]
                say_warning("Attempt to import a deleted dashboard!")
                say_warning("This may result in errors.")
              end

              if !data[:dashboard_elements]
                say_error("File contains no dashboard_elements! Is this a look?")
                raise Gzr::CLI::Error, "import file is not a valid dashboard"
              end

              dashboard = sync_dashboard(data,@dest_folder_id, output: output)
              say_warning "dashboard object #{JSON.pretty_generate dashboard.map(&:to_a).to_json}" if @options[:debug]

              dashboard[:dashboard_filters] ||= []
              source_filters = data[:dashboard_filters].sort { |a,b| a[:row] <=> b[:row] }
              source_filters.each do |new_filter|
                filter = new_filter.select do |k,v|
                  (keys_to_keep('create_dashboard_filter') + [:row]).include? k
                end
                filter[:dashboard_id] = dashboard[:id]
                say_warning "Creating filter" if @options[:debug]
                dashboard[:dashboard_filters].push create_dashboard_filter(filter)
              end

              dashboard[:dashboard_elements] ||= []
              elem_table = data[:dashboard_elements].map do |new_element|
                element = new_element.select do |k,v|
                  (keys_to_keep('create_dashboard_element') - [:dashboard_id, :look_id, :query_id, :merge_result_id, :result_maker_id, :query, :merge_result]).include? k
                end
                (element[:query_id],element[:look_id],element[:merge_result_id]) = process_dashboard_element(new_element)
                say_warning "Creating dashboard element #{element.select {|k,v| !v.nil?}.inspect}" if @options[:debug]
                element[:dashboard_id] = dashboard[:id]
                result_maker = copy_result_maker_filterables(new_element)
                element[:result_maker] = result_maker if result_maker
                dashboard_element = create_dashboard_element(element)
                say_warning "dashboard_element #{dashboard_element.inspect}" if @options[:debug]
                if new_element[:alerts]
                  new_element[:alerts].each do |a|
                    a.select! do |k,v|
                      (keys_to_keep('create_alert') - [:owner_id, :dashboard_element_id]).include? k
                    end
                    a[:dashboard_element_id] = dashboard_element[:id]
                    a[:owner_id] = @me[:id]
                    new_alert = create_alert(a)
                    say_warning "alert #{JSON.pretty_generate(new_alert)}" if @options[:debug]
                  end
                end
                dashboard[:dashboard_elements].push dashboard_element
                [new_element[:id], dashboard_element[:id]]
              end

              source_dashboard_layouts = data[:dashboard_layouts].map do |new_layout|
                layout_obj = nil
                if new_layout[:active]
                  layout_obj = get_dashboard_layout(dashboard[:dashboard_layouts].first[:id])
                  say_warning "Updating layout #{layout_obj[:id]}" if @options[:debug]
                else
                  layout = new_layout.select do |k,v|
                    (keys_to_keep('create_dashboard_layout') - [:dashboard_id]).include? k
                  end
                  layout[:dashboard_id] = dashboard[:id]
                  say_warning "Creating dashboard layout #{layout}" if @options[:debug]
                  layout_obj = create_dashboard_layout(layout)
                  say_warning "Created dashboard layout #{JSON.pretty_generate layout_obj.map(&:to_a).to_json}" if @options[:debug]
                end
                layout_components = new_layout[:dashboard_layout_components].zip(layout_obj[:dashboard_layout_components])
                layout_components.each do |source,target|
                  component = keys_to_keep('update_dashboard_layout_component').collect do |e|
                    [e,nil]
                  end.to_h
                  component[:dashboard_layout_id] = target[:dashboard_layout_id]

                  component.merge!(source.select do |k,v|
                    (keys_to_keep('update_dashboard_layout_component') - [:id,:dashboard_layout_id]).include? k
                  end)

                  component[:dashboard_element_id] = elem_table.assoc(source[:dashboard_element_id])[1]
                  say_warning "Updating dashboard layout component #{target[:id]}" if @options[:debug]
                  update_dashboard_layout_component(target[:id],component)
                end
              end
              upsert_plans_for_dashboard(dashboard[:id],@me[:id],data[:scheduled_plans]) if data[:scheduled_plans]
              output.puts "Imported dashboard #{dashboard[:id]}" unless @options[:plain]
              output.puts dashboard[:id] if @options[:plain]
            end
          end
        end

        def sync_dashboard(source, target_folder_id, output: $stdout)
          # try to find dashboard by slug in target folder
          existing_dashboard = search_dashboards_by_slug(source[:slug], target_folder_id).fetch(0,nil) if source[:slug]
          # check for dash of same title in target folder
          title_used = search_dashboards_by_title(source[:title], target_folder_id).select {|d| !d[:deleted] }.fetch(0,nil)
          # If there is no match by slug in target folder or no slug given, then we match by title
          existing_dashboard ||= title_used
          say_warning "existing_dashboard object #{existing_dashboard.inspect}" if @options[:debug]

          # same_title is now a flag indicating that there is already a dash in the same folder with
          # that title, and it is the one we are updating.
          same_title = (title_used&.fetch(:id,nil) == existing_dashboard&.fetch(:id,nil))

          # check if the slug is used by any dashboard
          slug_used = search_dashboards_by_slug(source[:slug]).fetch(0,nil) if source[:slug]

          # same_slug is now a flag indicating that there is already a dash with
          # that slug, but it is the one we are updating.
          same_slug = (slug_used&.fetch(:id,nil) == existing_dashboard&.fetch(:id,nil))

          if slug_used && !same_slug then
            say_warning "slug #{slug_used[:slug]} already used for dashboard #{slug_used[:title]} in folder #{slug_used[:folder_id]}", output: output
            say_warning("That dashboard is in the 'Trash' but not fully deleted yet", output: output) if slug_used[:deleted]
            say_warning "dashboard will be imported with new slug", output: output
          end

          if existing_dashboard then
            if title_used && !same_title then
              raise Gzr::CLI::Error, "Dashboard #{source[:title]} already exists in folder #{target_folder_id}\nDelete it before trying to upate another dashboard to have that title."
            end
            raise Gzr::CLI::Error, "Dashboard #{existing_dashboard[:title]} with slug #{existing_dashboard[:slug]} already exists in folder #{target_folder_id}\nUse --force if you want to overwrite it" unless @options[:force]

            say_ok "Modifying existing dashboard #{existing_dashboard[:id]} #{existing_dashboard[:title]} in folder #{target_folder_id}", output: output
            new_dash = source.select do |k,v|
              (keys_to_keep('update_dashboard') - [:space_id,:folder_id,:user_id,:slug]).include? k
            end
            new_dash[:slug] = source[:slug] unless slug_used
            new_dash[:deleted] = false if existing_dashboard[:deleted]
            d = update_dashboard(existing_dashboard[:id],new_dash)

            d[:dashboard_filters].each do |f|
              delete_dashboard_filter(f[:id])
            end
            d[:dashboard_filters] = []

            d[:dashboard_elements].each do |e|
              delete_dashboard_element(e[:id])
            end
            d[:dashboard_elements] = []

            d[:dashboard_layouts].each do |l|
              delete_dashboard_layout(l[:id]) unless l[:active]
            end
            d[:dashboard_layouts].select! { |l| l[:active] }

            return d
          else
            new_dash = source.select do |k,v|
              (keys_to_keep('create_dashboard') - [:space_id,:folder_id,:user_id,:slug]).include? k
            end
            new_dash[:slug] = source[:slug] unless slug_used
            new_dash[:folder_id] = target_folder_id
            new_dash[:user_id] = @me[:id]
            new_dash.select!{|k,v| !v.nil?}
            say_warning "new dashboard request #{new_dash.inspect}" if @options[:debug]
            d = create_dashboard(new_dash)
            say_warning "new dashboard object #{d.inspect}" if @options[:debug]
            return d
          end
        end

        def copy_result_maker_filterables(new_element)
          return nil unless new_element[:result_maker]
          if new_element[:result_maker].fetch(:filterables,[]).length > 0
            result_maker = { :filterables => [] }
            new_element[:result_maker][:filterables].each do |filterable|
              result_maker[:filterables] << filterable.select do |k,v|
                true unless [:can].include? k
              end
            end
            return result_maker
          end
          nil
        end

        def process_dashboard_element(dash_elem)
          return [nil, upsert_look(@me[:id], create_fetch_query(dash_elem[:look][:query])[:id], @dest_folder_id, dash_elem[:look])[:id], nil] if dash_elem[:look]

          query = dash_elem[:result_maker]&.fetch(:query, false) || dash_elem[:query]
          return [create_fetch_query(query)[:id], nil, nil] if query

          merge_result = dash_elem[:result_maker]&.fetch(:merge_result, false) || dash_elem[:merge_result]
          return [nil,nil,create_merge_result(merge_result)[:id]] if merge_result

          [nil,nil,nil]
        end

      end
    end
  end
end
