# frozen_string_literal: true

require "digest/sha2"
require "slack-notifier"

module Mihari
  module Emitters
    class Attachment
      prepend MemoWise

      # @return [String]
      attr_reader :data

      # @return [String]
      attr_reader :data_type

      #
      # @param [String] data
      # @param [String] data_type
      #
      def initialize(data:, data_type:)
        @data = data
        @data_type = data_type
      end

      def actions
        [vt_link, urlscan_link, censys_link, shodan_link].compact
      end

      def vt_link
        return nil unless _vt_link

        { type: "button", text: "VirusTotal", url: _vt_link }
      end

      def urlscan_link
        return nil unless _urlscan_link

        { type: "button", text: "urlscan.io", url: _urlscan_link }
      end

      def censys_link
        return nil unless _censys_link

        { type: "button", text: "Censys", url: _censys_link }
      end

      def shodan_link
        return nil unless _shodan_link

        { type: "button", text: "Shodan", url: _shodan_link }
      end

      # @return [Array]
      def to_a
        [
          {
            text: defanged_data,
            fallback: "VT & urlscan.io links",
            actions: actions
          }
        ]
      end

      private

      # @return [String, nil]
      def _urlscan_link
        case data_type
        when "ip"
          "https://urlscan.io/ip/#{data}"
        when "domain"
          "https://urlscan.io/domain/#{data}"
        when "url"
          uri = Addressable::URI.parse(data)
          "https://urlscan.io/domain/#{uri.hostname}"
        end
      end
      memo_wise :_urlscan_link

      # @return [String, nil]
      def _vt_link
        case data_type
        when "hash"
          "https://www.virustotal.com/#/file/#{data}"
        when "ip"
          "https://www.virustotal.com/#/ip-address/#{data}"
        when "domain"
          "https://www.virustotal.com/#/domain/#{data}"
        when "url"
          "https://www.virustotal.com/#/url/#{sha256}"
        when "mail"
          "https://www.virustotal.com/#/search/#{data}"
        end
      end
      memo_wise :_vt_link

      # @return [String, nil]
      def _censys_link
        (data_type == "ip") ? "https://search.censys.io/hosts/#{data}" : nil
      end
      memo_wise :_censys_link

      # @return [String, nil]
      def _shodan_link
        (data_type == "ip") ? "https://www.shodan.io/host/#{data}" : nil
      end
      memo_wise :_shodan_link

      # @return [String]
      def sha256
        Digest::SHA256.hexdigest data
      end

      # @return [String]
      def defanged_data
        @defanged_data ||= data.to_s.gsub(".", "[.]")
      end
    end

    class Slack < Base
      DEFAULT_CHANNEL = "#general"
      DEFAULT_USERNAME = "Mihari"

      # @return [String, nil]
      attr_reader :webhook_url

      # @return [String]
      attr_reader :channel

      # @return [String]
      attr_reader :username

      # @return [Array<Mihari::Models::Artifact>]
      attr_accessor :artifacts

      #
      # @param [Mihari::Rule] rule
      # @param [Hash, nil] options
      # @param [Hash, nil] params
      #
      def initialize(rule:, options: nil, **params)
        super(rule: rule, options: options)

        @webhook_url = params[:webhook_url] || Mihari.config.slack_webhook_url
        @channel = params[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
        @username = DEFAULT_USERNAME

        @artifacts = []
      end

      #
      # Check webhook URL is set
      #
      # @return [Boolean]
      #
      def webhook_url?
        !webhook_url.nil?
      end

      #
      # @return [Boolean]
      #
      def configured?
        webhook_url?
      end

      #
      # @return [String]
      #
      def target
        channel
      end

      #
      # @return [::Slack::Notifier]
      #
      def notifier
        @notifier ||= lambda do
          return ::Slack::Notifier.new(webhook_url, channel: channel, username: username) if timeout.nil?

          ::Slack::Notifier.new(webhook_url, channel: channel, username: username, http_options: { timeout: timeout })
        end.call
      end

      #
      # Build attachments
      #
      # @return [Array<Mihari::Emitters::Attachment>]
      #
      def attachments
        artifacts.map { |artifact| Attachment.new(data: artifact.data, data_type: artifact.data_type).to_a }.flatten
      end

      #
      # Build a text
      #
      # @return [String]
      #
      def text
        tags = rule.tags.map(&:name)
        tags = ["N/A"] if tags.empty?
        [
          "*#{rule.title}*",
          "*Desc.*: #{rule.description}",
          "*Tags*: #{tags.join(", ")}"
        ].join("\n")
      end

      #
      # @param [Array<Mihari::Models::Artifact>] artifacts
      #
      def call(artifacts)
        return if artifacts.empty?

        @artifacts = artifacts

        notifier.post(text: text, attachments: attachments, mrkdwn: true)
      end
    end
  end
end