# frozen_string_literal: true

class Permalink < ActiveRecord::Base
  attr_accessor :permalink_type, :permalink_type_value

  belongs_to :topic
  belongs_to :post
  belongs_to :category
  belongs_to :tag
  belongs_to :user

  before_validation :clear_associations
  before_validation :normalize_url, :encode_url
  before_validation :set_association_value

  validates :url, uniqueness: true

  validates :topic_id, presence: true, if: Proc.new { |permalink| permalink.topic_type? }
  validates :post_id, presence: true, if: Proc.new { |permalink| permalink.post_type? }
  validates :category_id, presence: true, if: Proc.new { |permalink| permalink.category_type? }
  validates :tag_id, presence: true, if: Proc.new { |permalink| permalink.tag_type? }
  validates :user_id, presence: true, if: Proc.new { |permalink| permalink.user_type? }
  validates :external_url, presence: true, if: Proc.new { |permalink| permalink.external_url_type? }

  %i[topic post category tag user external_url].each do |association|
    define_method("#{association}_type?") { self.permalink_type == association.to_s }
  end

  class Normalizer
    attr_reader :source

    def initialize(source)
      @source = source
      @rules = source.split("|").map { |rule| parse_rule(rule) }.compact if source.present?
    end

    def parse_rule(rule)
      return unless rule =~ %r{/.*/}

      escaping = false
      regex = +""
      sub = +""
      c = 0

      rule.chars.each do |l|
        c += 1 if !escaping && l == "/"
        escaping = l == "\\"

        if c > 1
          sub << l
        else
          regex << l
        end
      end

      [Regexp.new(regex[1..-1]), sub[1..-1] || ""] if regex.length > 1
    end

    def normalize(url)
      return url unless @rules
      @rules.each { |(regex, sub)| url = url.sub(regex, sub) }

      url
    end
  end

  def self.normalize_url(url)
    if url
      url = url.strip
      url = url[1..-1] if url[0, 1] == "/"
    end

    normalizations = SiteSetting.permalink_normalizations

    @normalizer = Normalizer.new(normalizations) unless @normalizer &&
      @normalizer.source == normalizations
    @normalizer.normalize(url)
  end

  def self.find_by_url(url)
    find_by(url: normalize_url(url))
  end

  def target_url
    return relative_external_url if external_url
    return post.relative_url if post
    return topic.relative_url if topic
    return category.relative_url if category
    return tag.relative_url if tag
    return user.relative_url if user
    nil
  end

  def self.filter_by(url = nil)
    permalinks =
      Permalink.includes(:topic, :post, :category, :tag, :user).order("permalinks.created_at desc")

    permalinks.where!("url ILIKE :url OR external_url ILIKE :url", url: "%#{url}%") if url.present?
    permalinks.limit!(100)
    permalinks.to_a
  end

  private

  def normalize_url
    self.url = Permalink.normalize_url(url) if url
  end

  def encode_url
    self.url = UrlHelper.encode(url) if url
  end

  def relative_external_url
    external_url.match?(%r{\A/[^/]}) ? "#{Discourse.base_path}#{external_url}" : external_url
  end

  def clear_associations
    self.topic_id = nil
    self.post_id = nil
    self.category_id = nil
    self.user_id = nil
    self.tag_id = nil
    self.external_url = nil
  end

  def set_association_value
    self.topic_id = self.permalink_type_value if self.topic_type?
    self.post_id = self.permalink_type_value if self.post_type?
    self.user_id = self.permalink_type_value if self.user_type?
    self.category_id = self.permalink_type_value if self.category_type?
    self.external_url = self.permalink_type_value if self.external_url_type?
    self.tag_id = Tag.where(name: self.permalink_type_value).first&.id if self.tag_type?
  end
end

# == Schema Information
#
# Table name: permalinks
#
#  id           :integer          not null, primary key
#  url          :string(1000)     not null
#  topic_id     :integer
#  post_id      :integer
#  category_id  :integer
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#  external_url :string(1000)
#  tag_id       :integer
#  user_id      :integer
#
# Indexes
#
#  index_permalinks_on_url  (url) UNIQUE
#