# frozen_string_literal: true

require 'concurrent'
require_relative 'base'

module Mastodon::CLI
  class EmailDomainBlocks < Base
    option :only_blocked, type: :boolean, default: false
    option :only_with_approval, type: :boolean, default: false
    desc 'list', 'List blocked e-mail domains'
    long_desc <<-LONG_DESC
      By default this command lists all domains in the email domain block list
      and their associated MX records (if included).

      If the --only-blocked option is provided, this command will list only email
      domains that are fully blocked from signup.

      If the --only-with-approval option is provided, this command will list only
      email domains that are allowed to be used but require manual approval.

      The --only-blocked and --only-with-approval options are mutually exclusive.
    LONG_DESC
    def list
      fail_with_message 'Cannot specify both --only-blocked and --only-with-approval' if options[:only_blocked] && options[:only_with_approval]

      base_query = EmailDomainBlock.parents

      if options[:only_blocked]
        base_query = base_query.where(allow_with_approval: false)
      elsif options[:only_with_approval]
        base_query = base_query.where(allow_with_approval: true)
      end

      base_query.find_each do |parent|
        say(parent.domain.to_s, :white)

        shell.indent do
          EmailDomainBlock.where(parent_id: parent.id).find_each do |child|
            say(child.domain, :cyan)
          end
        end
      end
    end

    option :with_dns_records, type: :boolean
    option :allow_with_approval, type: :boolean, default: false
    desc 'add DOMAIN...', 'Block e-mail domain(s)'
    long_desc <<-LONG_DESC
      Blocking an e-mail domain prevents users from signing up
      with e-mail addresses from that domain. You can provide one or
      multiple domains to the command.

      When the --with-dns-records option is given, an attempt to resolve the
      given domains' MX records will be made and the results will also be blocked.
      This can be helpful if you are blocking an e-mail server that has many
      different domains pointing to it as it allows you to essentially block
      it at the root.

      When the --allow-with-approval option is set, the email domains provided will
      have to be manually approved for signup.
    LONG_DESC
    def add(*domains)
      fail_with_message 'No domain(s) given' if domains.empty?

      skipped = 0
      processed = 0

      domains.each do |domain|
        if EmailDomainBlock.exists?(domain: domain)
          say("#{domain} is already blocked.", :yellow)
          skipped += 1
          next
        end

        other_domains = []
        other_domains = DomainResource.new(domain).mx if options[:with_dns_records]

        email_domain_block = EmailDomainBlock.new(domain: domain, other_domains: other_domains, allow_with_approval: options[:allow_with_approval])
        email_domain_block.save!
        processed += 1

        (email_domain_block.other_domains || []).uniq.each do |hostname|
          if EmailDomainBlock.exists?(domain: hostname)
            say("#{hostname} is already blocked.", :yellow)
            skipped += 1
            next
          end

          another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block, allow_with_approval: options[:allow_with_approval])
          another_email_domain_block.save!
          processed += 1
        end
      end

      say("Added #{processed}, skipped #{skipped}", color(processed, 0))
    end

    desc 'remove DOMAIN...', 'Remove e-mail domain blocks'
    def remove(*domains)
      fail_with_message 'No domain(s) given' if domains.empty?

      skipped = 0
      processed = 0
      failed = 0

      domains.each do |domain|
        entry = EmailDomainBlock.find_by(domain: domain)

        if entry.nil?
          say("#{domain} is not yet blocked.", :yellow)
          skipped += 1
          next
        end

        children_count = EmailDomainBlock.where(parent_id: entry.id).count
        result = entry.destroy

        if result
          processed += children_count + 1
        else
          say("#{domain} could not be unblocked.", :red)
          failed += 1
        end
      end

      say("Removed #{processed}, skipped #{skipped}, failed #{failed}", color(processed, failed))
    end

    private

    def color(processed, failed)
      if !processed.zero? && failed.zero?
        :green
      elsif failed.zero?
        :yellow
      else
        :red
      end
    end
  end
end
