class NicoTagsController < ApplicationController
  def index
    name = params[:name].presence
    linked_tag = params[:linked_tag].presence
    link_status = params[:link_status].presence
    order = params[:order].to_s.split(':', 2).map(&:strip)
    order[0] = 'updated_at' unless order[0].in?(['name', 'created_at', 'updated_at'])
    unless order[1].in?(['asc', 'desc'])
      order[1] = order[0] == 'name' ? 'asc' : 'desc'
    end
    page = (params[:page].presence || 1).to_i
    limit = (params[:limit].presence || 20).to_i

    page = 1 if page < 1
    limit = 1 if limit < 1

    post_tag_max_sql =
        PostTag
        .select('tag_id, MAX(created_at) AS max_created_at')
        .group('tag_id')
        .to_sql

    q = Tag.nico_tags
           .joins(:tag_name)
           .joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
                  'ON post_tag_max.tag_id = tags.id')
           .includes(:tag_name, tag_name: :wiki_page, linked_tags: { tag_name: :wiki_page })
    q = q.where('tag_names.name LIKE ?', "%#{ name }%") if name
    if linked_tag
      linked_tag_ids =
        Tag
        .joins(:tag_name)
        .where('tag_names.name LIKE ?', "%#{ linked_tag }%")
        .pluck(:id)
      linked_nico_tag_ids = NicoTagRelation.where(tag_id: linked_tag_ids).pluck(:nico_tag_id)
      q = q.where(id: linked_nico_tag_ids)
    end
    if link_status.in?(['linked', 'unlinked'])
      exists_sql =
          'EXISTS (SELECT 1 FROM nico_tag_relations ' \
          'WHERE nico_tag_relations.nico_tag_id = tags.id)'
      q = link_status == 'linked' ? q.where(exists_sql) : q.where("NOT #{ exists_sql }")
    end

    count = q.count
    sort_sql =
        case order[0]
        when 'name'
          'tag_names.name'
        when 'updated_at'
          'post_tag_max.max_created_at'
        else
          "tags.#{ order[0] }"
        end
    tags = q.reselect('tags.*',
                      Arel.sql('post_tag_max.max_created_at AS recent_post_tag_created_at'))
            .order(Arel.sql("#{ sort_sql } #{ order[1] }, tags.id #{ order[1] }"))
            .limit(limit)
            .offset((page - 1) * limit)
            .to_a

    render json: { tags: tags.map { |tag|
      TagRepr.base(tag).merge(
          recent_post_tag_created_at: tag.recent_post_tag_created_at,
          linked_tags: tag.linked_tags.map { |lt| TagRepr.base(lt) })
    }, count: }
  end

  def update
    return head :unauthorized unless current_user
    return head :forbidden unless current_user.gte_member?

    id = params[:id].to_i

    tag = Tag.find(id)
    return render_bad_request('ニコニコ・タグを指定してください．') unless tag.nico?

    linked_tag_names = params[:tags].to_s.split
    linked_tags = nil

    ApplicationRecord.transaction do
      linked_tags = Tag.normalise_tags!(linked_tag_names, with_tagme: false,
                                                          with_no_deerjikist: false)
      if linked_tags.any? { |t| t.nico? }
        raise Tag::NicoTagNormalisationError
      end

      TagVersioning.record_tag_snapshots!(linked_tags, created_by_user: current_user)

      tag.linked_tags = linked_tags
      tag.save!

      NicoTagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user)
    end

    render json: tag.linked_tags.map { |t| TagRepr.base(t) }, status: :ok
  rescue Tag::NicoTagNormalisationError
    render_validation_error fields: { tags: ['ニコニコ・タグ同士は連携できません．'] }
  rescue ActiveRecord::RecordInvalid => e
    render_nico_tag_form_record_invalid e.record
  end

  private

  def render_nico_tag_form_record_invalid record
    if record.is_a?(TagName) || record.is_a?(Tag)
      render_validation_error fields: { tags: record.errors.full_messages.map { |message|
        "タグ名 “#{ record.name }”: #{ message }"
      } }
    else
      render_validation_error record
    end
  end
end
