class Post < ApplicationRecord
  require 'mini_magick'

  belongs_to :uploaded_user, class_name: 'User', optional: true

  has_many :post_tags, dependent: :destroy, inverse_of: :post
  has_many :active_post_tags, -> { kept }, class_name: 'PostTag', inverse_of: :post
  has_many :post_tags_with_discarded, -> { with_discarded }, class_name: 'PostTag'
  has_many :tags, through: :active_post_tags
  has_many :active_tags, -> { where(tags: { deprecated_at: nil }) },
           through: :active_post_tags, source: :tag

  has_many :user_post_views, dependent: :delete_all
  has_many :post_similarities, dependent: :delete_all
  has_many :post_versions
  has_many :gekanator_guessed_games,
           class_name: 'GekanatorGame',
           foreign_key: :guessed_post_id,
           dependent: :delete_all,
           inverse_of: :guessed_post
  has_many :gekanator_correct_games,
           class_name: 'GekanatorGame',
           foreign_key: :correct_post_id,
           dependent: :delete_all,
           inverse_of: :correct_post
  has_many :gekanator_question_examples, dependent: :delete_all

  has_many :parent_post_implications,
           class_name: 'PostImplication',
           foreign_key: :post_id,
           dependent: :destroy,
           inverse_of: :post
  has_many :parents, through: :parent_post_implications, source: :parent_post

  has_many :child_post_implications,
           class_name: 'PostImplication',
           foreign_key: :parent_post_id,
           dependent: :destroy,
           inverse_of: :parent_post
  has_many :children, through: :child_post_implications, source: :post

  has_one_attached :thumbnail

  attribute :version_no, :integer, default: 1

  before_validation :normalise_url

  validates :url, presence: true, uniqueness: true

  validate :validate_original_created_range
  validate :url_must_be_http_url

  def parent_posts = parents

  def child_posts = children

  def sibling_posts
    parent_post_ids = parent_posts.order(:id).pluck(:id)

    parent_post_ids.to_h { [_1, PostImplication.where(parent_post: _1).map(&:post)] }
  end

  def as_json options = { }
    super(options).merge(thumbnail: thumbnail.attached? ?
                                    Rails.application.routes.url_helpers.rails_blob_url(
                                        thumbnail, only_path: false) :
                                    nil)
  rescue
    super(options).merge(thumbnail: nil)
  end

  def snapshot_tag_names
    post_tags
        .kept
        .joins(tag: :tag_name)
        .includes(:sections, tag: :tag_name)
        .order('tag_names.name')
        .map do |post_tag|
      name = post_tag.tag.tag_name.name
      sections = post_tag.sections.sort_by(&:begin_ms)

      next name if sections.empty?

      "#{ name }#{ sections.map { Post.section_literal(_1) }.join }"
    end
  end

  def self.section_literal section
    "[#{ Post.ms_to_time(section.begin_ms) }-#{ section.end_ms ? Post.ms_to_time(section.end_ms) : '' }]"
  end

  def self.ms_to_time ms
    total_s = ms / 1_000
    s = total_s % 60
    min = (total_s / 60) % 60
    h = total_s / 3_600

    if h.positive?
      '%d:%02d:%02d' % [h, min, s]
    else
      '%d:%02d' % [min, s]
    end
  end

  def snapshot_parent_post_ids = parents.order(:id).pluck(:id)

  def related limit: nil
    ids = post_similarities.order(cos: :desc)
    ids = ids.limit(limit) if limit
    ids = ids.pluck(:target_post_id)
    return Post.none if ids.empty?

    Post.where(id: ids)
        .with_attached_thumbnail
        .order(Arel.sql("FIELD(posts.id, #{ ids.join(',') })"))
  end

  def resized_thumbnail!
    return unless thumbnail.attached?

    image = MiniMagick::Image.read(thumbnail.download)
    image.resize '180x180'
    thumbnail.purge
    thumbnail.attach(io:            File.open(image.path),
                     filename:      'resized_thumbnail.jpg',
                     content_type:  'image/jpeg')
  end

  private

  def validate_original_created_range
    f = original_created_from
    b = original_created_before
    return if f.blank? || b.blank?

    f = Time.zone.parse(f) if String === f
    b = Time.zone.parse(b) if String === b
    return if !(f) || !(b)

    if f >= b
      errors.add :original_created_at, 'オリジナルの作成日時の順番がをかしぃです．'
    end
  end

  def url_must_be_http_url
    begin
      u = URI.parse(url)
    rescue URI::InvalidURIError
      errors.add(:url, 'URL が不正です．')
      return
    end

    if !(u in URI::HTTP) || u.host.blank?
      errors.add(:url, 'URL が不正です．')
      return
    end
  end

  def normalise_url
    return if url.blank?

    self.url = url.strip

    u = URI.parse(url)
    return unless u in URI::HTTP

    u.host = u.host.downcase if u.host
    u.path = u.path.sub(/\/\Z/, '') if u.path.present?
    self.url = u.to_s
  rescue URI::InvalidURIError
    ;
  end
end
