require 'resolv'

class WebsiteBrandingService
  include SocialLinkParser

  attr_reader :http_status

  DATA_DEFAULTS = { description: nil, slogan: nil, phone: nil, address: nil, links: nil, stock: nil, industries: [], is_nsfw: false }.freeze

  def initialize(email)
    @email = email
    @domain = email.split('@').last&.downcase&.strip
    @url = "https://#{@domain}"
    @http_status = nil
  end

  def perform
    doc = fetch_page
    return nil if doc.nil?

    links = extract_links(doc)

    DATA_DEFAULTS.merge({
                          domain: @domain,
                          title: extract_title(doc),
                          colors: extract_colors(doc),
                          logos: extract_logos(doc),
                          socials: build_socials(links),
                          email: @email,
                          email_provider: detect_email_provider
                        })
  rescue StandardError => e
    Rails.logger.error "[WebsiteBranding] #{e.message}"
    nil
  end

  private

  def fetch_page
    body = nil
    SafeFetch.fetch(@url, validate_content_type: false) do |result|
      body = result.tempfile.read
    end
    @http_status = 200
    return nil if body.blank?

    Nokogiri::HTML(body)
  rescue SafeFetch::HttpError => e
    @http_status = e.message.to_i
    nil
  rescue SafeFetch::Error => e
    Rails.logger.error "[WebsiteBranding] Failed to fetch #{@url}: #{e.message}"
    nil
  end

  def extract_title(doc)
    og_site_name = doc.at_css('meta[property="og:site_name"]')&.[]('content')
    return og_site_name.strip if og_site_name.present?

    title = doc.at_xpath('//title')&.text
    title&.strip&.split(/\s*[|\-–—·:]+\s*/)&.first
  end

  def extract_colors(doc)
    color = doc.at_css('meta[name="theme-color"]')&.[]('content')
    return [] if color.blank?

    [{ hex: color, name: nil }]
  end

  def extract_logos(doc)
    favicon = doc.at_css('link[rel*="icon"]')&.[]('href')
    return [] if favicon.blank?

    url = resolve_url(favicon)
    return [] if url.blank?

    [{ url: url, type: nil, mode: nil, colors: [], resolution: { aspect_ratio: 1 } }]
  end

  def build_socials(links)
    handles = extract_social_from_links(links)
    handles.filter_map do |platform, handle|
      next if handle.blank?

      url = reconstruct_social_url(platform, handle)
      { type: platform.to_s, url: url }
    end
  end

  def reconstruct_social_url(platform, handle)
    base_urls = { whatsapp: 'https://wa.me/', line: 'https://line.me/', facebook: 'https://facebook.com/',
                  instagram: 'https://instagram.com/', telegram: 'https://t.me/', tiktok: 'https://tiktok.com/' }
    "#{base_urls[platform]}#{handle}"
  end

  def extract_links(doc)
    doc.css('a[href]').filter_map do |a|
      href = a['href']&.strip
      next if href.blank? || href.start_with?('#', 'javascript:', 'mailto:', 'tel:')

      href.start_with?('http') ? href : URI.join(@url, href).to_s
    rescue URI::InvalidURIError
      nil
    end.uniq
  end

  def resolve_url(url)
    return nil if url.blank?
    return url if url.start_with?('http')

    URI.join(@url, url).to_s
  rescue URI::InvalidURIError
    nil
  end

  GOOGLE_MX_DOMAINS = %w[google.com googlemail.com].freeze
  MICROSOFT_MX_DOMAINS = %w[outlook.com].freeze

  # Probes the domain's MX records to infer the mailbox provider, returning
  # 'google' or 'microsoft' (matching Channel::Email#provider) or nil when unknown.
  def detect_email_provider
    hosts = mx_records
    return 'google' if mx_hosted_by?(hosts, GOOGLE_MX_DOMAINS)
    return 'microsoft' if mx_hosted_by?(hosts, MICROSOFT_MX_DOMAINS)

    nil
  end

  # Matches on the registrable domain of the MX host (anchored on a label
  # boundary) so lookalikes like "notgoogle.com" don't get misclassified.
  def mx_hosted_by?(hosts, provider_domains)
    hosts.any? do |host|
      provider_domains.any? { |domain| host == domain || host.end_with?(".#{domain}") }
    end
  end

  def mx_records
    Resolv::DNS.open do |resolver|
      resolver.timeouts = 5
      resolver.getresources(@domain, Resolv::DNS::Resource::IN::MX).map { |record| record.exchange.to_s.downcase }
    end
  rescue StandardError => e
    Rails.logger.error "[WebsiteBranding] MX probe failed for #{@domain}: #{e.message}"
    []
  end
end

WebsiteBrandingService.prepend_mod_with('WebsiteBrandingService')
