# frozen_string_literal: true

require "csv"

module Decidim
  module Accountability
    # This class handles importing results from a CSV file.
    # Needs a `current_component` param with a `Decidim::component`
    # in order to import the results in that component.
    class ResultsCsvImporter
      include Decidim::FormFactory

      # Public: Initializes the service.
      # component     - A Decidim::component to import the results into.
      # csv_file      - The contents of the CSV to read.
      def initialize(component, csv_file, current_user)
        @component = component
        @csv_file = csv_file

        @extra_context = {
          current_component: component,
          current_organization: component.organization,
          current_user:,
          current_participatory_space: component.participatory_space
        }
        @matches_ids = []
      end

      def import!
        errors = []

        ActiveRecord::Base.transaction do
          i = 1
          csv = CSV.new(@csv_file, headers: true, col_sep: Decidim.default_csv_col_sep)
          while (row = csv.shift).present?
            i += 1
            next if row.empty?

            params = set_params_for_import_result_form(row, @component)
            existing_result = Decidim::Accountability::Result.find_by(id: row["id"], component: @component) if row["id"].present?
            params["result"].merge!(parse_date_params(row, "start_date"))
            params["result"].merge!(parse_date_params(row, "end_date"))
            @form = form(Decidim::Accountability::Admin::ResultForm).from_params(params, @extra_context)
            errors << [i, @form.errors.full_messages] if @form.errors.any?

            if existing_result.present?
              Decidim::Accountability::Admin::UpdateImportedResult.call(@form, existing_result, params["result"]["parent/id"]) do
                on(:invalid) do
                  errors << [i, @form.errors.full_messages]
                end
              end
            else
              Decidim::Accountability::Admin::CreateImportedResult.call(@form, params["result"]["parent/id"]) do
                on(:invalid) do
                  errors << [i, @form.errors.full_messages]
                end
              end
              add_match_id(row["id"])
            end

          end

          raise ActiveRecord::Rollback if errors.any?

          update_parents
          remove_invalid_results

          Rails.logger.info "Processed: #{i}"
        end

        errors
      end

      private

      def set_params_for_import_result_form(row, component)
        params = {}
        params["result"] = row.to_hash
        default_locale = component.participatory_space.organization.default_locale
        available_locales = component.participatory_space.organization.available_locales
        params["result"].merge!(get_locale_attributes(default_locale, available_locales, :title, row))
        params["result"].merge!(get_locale_attributes(default_locale, available_locales, :description, row))
        params["result"]["taxonomies"] = row["taxonomies/ids"].split(",") if row["taxonomies/ids"].present?
        params["result"]["decidim_accountability_status_id"] = row["status/id"] if row["status/id"].present?
        params["result"].merge!(get_proposal_ids(row["proposal_urls"]))
        params
      end

      def get_locale_attributes(default_locale, available_locales, field, row)
        array_field_localized = available_locales.map do |locale|
          if row["#{field}/#{locale}"].present?
            ["#{field}_#{locale}", row["#{field}/#{locale}"]]
          else
            ["#{field}_#{locale}", row["#{field}/#{default_locale}"]]
          end
        end

        Hash[*array_field_localized.flatten]
      end

      def parse_date_params(row, field)
        begin
          return { field => Date.parse(row[field]) } if row[field].present?
        rescue ArgumentError
          @form.errors.add(field.to_sym, :invalid_date)
        end
        {}
      end

      def get_proposal_ids(proposal_urls)
        if proposal_urls.present?
          proposal_urls = proposal_urls.split(";")
          { "proposal_ids" => proposal_urls.map { |proposal_url| proposal_url.scan(/\d$/).first.to_i } }
        else
          {}
        end
      end

      def add_match_id(id)
        last_created_result = Decidim::Accountability::Result.last
        @matches_ids << [id, Decidim::Accountability::Result.last.id] if id.present? && last_created_result.present?
      end

      def update_parents
        @matches_ids.each do |match|
          Decidim::Accountability::Result.where(component: @component, parent_id: match.first).find_each { |result| result.update(parent_id: match.last) }
        end
      end

      def remove_invalid_results
        Decidim::Accountability::Result.includes(:parent).references(:parent)
                                       .where(parents_decidim_accountability_results: { id: nil })
                                       .where.not(parent_id: nil).each do |result|
          Decidim::Commands::DestroyResource.call(result, @extra_context[:current_user])
        end
      end
    end
  end
end
