class WikiPagesController < ApplicationController
  rescue_from Wiki::Commit::Conflict, with: :render_wiki_conflict

  def index
    title = params[:title].to_s.strip
    if title.blank?
      return render json: WikiPageRepr.base(
          WikiPage.joins(:tag_name).includes(tag_name: :tag))
    end

    q = WikiPage.joins(:tag_name).includes(tag_name: :tag)
        .where('tag_names.name LIKE ?', "%#{ WikiPage.sanitize_sql_like(title) }%")
    render json: WikiPageRepr.base(q.limit(20))
  end

  def show
    page = WikiPage.joins(:tag_name)
                   .includes(tag_name: :tag)
                   .find_by(id: params[:id])
    render_wiki_page_or_404 page
  end

  def show_by_title
    title = params[:title].to_s.strip
    page = WikiPage.joins(:tag_name)
                   .includes(tag_name: :tag)
                   .find_by(tag_name: { name: title })
    render_wiki_page_or_404 page
  end

  def exists
    if WikiPage.exists?(id: params[:id])
      head :no_content
    else
      head :not_found
    end
  end

  def exists_by_title
    title = params[:title].to_s.strip
    if WikiPage.joins(:tag_name).exists?(tag_names: { name: title })
      head :no_content
    else
      head :not_found
    end
  end

  def diff
    id = params[:id]
    return render_bad_request('id は必須です．') if id.blank?

    from = params[:from].presence
    to = params[:to].presence

    page = WikiPage.joins(:tag_name).includes(tag_name: :tag).find(id)

    from_rev = from && page.wiki_revisions.find(from)
    to_rev = to ? page.wiki_revisions.find(to) : page.current_revision
    if ((from_rev && !(from_rev.content?)) || !(to_rev&.content?))
      return render_unprocessable_entity('差分を表示できない版です．')
    end

    diffs = Diff::LCS.sdiff(from_rev&.body&.lines || [], to_rev.body.lines)
    diff_json = diffs.map { |change|
      case change.action
      when ?=
        { type: 'context', content: change.old_element }
      when ?!
        [{ type: 'removed', content: change.old_element },
         { type: 'added', content: change.new_element }]
      when ?+
        { type: 'added', content: change.new_element }
      when ?-
        { type: 'removed', content: change.old_element }
      end
    }.flatten.compact

    render json: { wiki_page_id:      page.id,
                   title:             page.title,
                   deprecated_at:     page.deprecated_at,
                   older_revision_id: from_rev&.id,
                   newer_revision_id: to_rev.id,
                   diff:              diff_json }
  end

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

    title = params[:title].to_s.strip
    body = params[:body].to_s
    message = params[:message].presence

    return render_unprocessable_entity('タイトルは必須です．', field: :title) if title.blank?
    return render_unprocessable_entity('本文は必須です．', field: :body) if body.blank?

    tag_name = TagName.find_undiscard_or_create_by!(name: title)

    page =
        Wiki::Commit.create_content!(
            tag_name:,
            body:,
            created_by_user: current_user,
            message:)

    render json: WikiPageRepr.base(page), status: :created
  rescue ActiveRecord::RecordInvalid => e
    render_validation_error e.record
  rescue ActiveRecord::RecordNotUnique
    render_record_not_unique
  end

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

    title = params[:title]&.strip
    body = params[:body].to_s

    return render_unprocessable_entity('タイトルは必須です．', field: :title) if title.blank?
    return render_unprocessable_entity('本文は必須です．', field: :body) if body.blank?

    page = WikiPage.find(params[:id])
    base_revision_id = params[:base_revision_id].presence

    ApplicationRecord.transaction do
      page.lock!

      old_title = page.title

      tag = Tag.find_by(tag_name_id: page.tag_name_id)

      if tag && title != old_title
        TagVersioning.ensure_snapshot!(tag, created_by_user: current_user)
      end

      page.tag_name.update!(name: title) if title != old_title

      message = params[:message].presence
      Wiki::Commit.content!(page:,
                            body:,
                            created_user: current_user,
                            message:,
                            base_revision_id:)

      if tag && title != old_title
        tag.reload
        TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user)
      end
    end

    head :ok
  end

  def search
    index
  end

  def changes
    id = params[:id].presence
    q = WikiRevision.joins(wiki_page: :tag_name)
                    .includes(:created_user, wiki_page: { tag_name: :tag })
                    .order(id: :desc)
    q = q.where(wiki_page_id: id) if id

    render json: q.limit(200).map { |rev|
      { revision_id:  rev.id,
        pred:         rev.base_revision_id,
        succ:         nil,
        wiki_page:    { id: rev.wiki_page_id,
                        title: rev.wiki_page.title,
                        deprecated_at: rev.wiki_page.deprecated_at },
        user:         rev.created_user && { id: rev.created_user.id, name: rev.created_user.name },
        kind:         rev.kind,
        message:      rev.message,
        timestamp:    rev.created_at }
    }.compact
  end

  private

  def render_wiki_page_or_404 page
    return head :not_found unless page

    rev = find_revision(page)
    return head :not_found unless rev

    if rev.redirect?
      return (
          redirect_to wiki_page_by_title_path(title: rev.redirect_page.title),
                      status: :moved_permanently)
    end

    body = rev.body
    revision_id = rev.id
    pred = page.pred_revision_id(revision_id)
    succ = page.succ_revision_id(revision_id)
    updated_at = rev.created_at

    render json: WikiPageRepr.base(page).merge(body:, revision_id:, pred:, succ:, updated_at:)
  end

  def find_revision page
    if params[:version].present?
      page.wiki_revisions.find_by(id: params[:version])
    else
      page.current_revision
    end
  end

  def render_wiki_conflict err
    render json: { error: 'conflict', message: err.message }, status: :conflict
  end
end
