class TheatresController < ApplicationController
  def show
    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre

    render json: TheatreRepr.base(theatre)
  end

  def watching
    return head :unauthorized unless current_user

    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre

    host_flg = false
    post_id = nil
    post_started_at = nil

    theatre.with_lock do
      TheatreWatchingUser.find_or_initialize_by(theatre:, user: current_user).tap {
        _1.expires_at = 30.seconds.from_now
      }.save!

      if (!(theatre.host_user_id?) ||
          !(theatre.watching_users.exists?(id: theatre.host_user_id)))
        theatre.update!(host_user_id: current_user.id)
      end

      host_flg = theatre.host_user_id == current_user.id
      post_id = theatre.current_post_id
      post_started_at = theatre.current_post_started_at
    end

    render json: theatre_info_json(theatre, host_flg:, post_id:, post_started_at:)
  end

  def next_post
    return head :unauthorized unless current_user

    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre
    return head :forbidden if theatre.host_user != current_user

    ApplicationRecord.transaction do
      theatre.lock!
      TheatrePostAdvancer.call(theatre:)
    end

    head :no_content
  end

  def skip_vote
    return head :unauthorized unless current_user

    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre
    requested_post_id = params[:post_id].to_i
    return head :unprocessable_entity if requested_post_id <= 0

    skipped = false
    conflicted = false

    ApplicationRecord.transaction do
      theatre.lock!
      if theatre.current_post
        TheatreWatchingUser.find_or_initialize_by(theatre:, user: current_user).tap {
          _1.expires_at = 30.seconds.from_now
        }.save!

        if theatre.current_post_id != requested_post_id
          conflicted = true
          next
        end

        TheatreSkipVote.find_or_create_by!(theatre:, post_id: requested_post_id, user: current_user)

        vote_status = skip_vote_status(theatre)
        if vote_status[:votes_count] >= vote_status[:required_count]
          TheatreSkipFinalizer.call(theatre:, user: current_user)
          TheatrePostAdvancer.call(theatre:)
          skipped = true
        end
      end
    end

    theatre.reload
    return render json: theatre_info_json(theatre, skipped: false), status: :conflict if conflicted

    render json: theatre_info_json(theatre, skipped:)
  end

  def unskip_vote
    return head :unauthorized unless current_user

    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre
    requested_post_id = params[:post_id].to_i
    return head :unprocessable_entity if requested_post_id <= 0

    conflicted = false

    theatre.with_lock do
      if theatre.current_post
        if theatre.current_post_id != requested_post_id
          conflicted = true
        else
          TheatreSkipVote.where(theatre:, post_id: requested_post_id, user: current_user).delete_all
        end
      end
    end

    theatre.reload
    return render json: theatre_info_json(theatre, skipped: false), status: :conflict if conflicted

    render json: theatre_info_json(theatre, skipped: false)
  end

  def post_selection_weights
    theatre = Theatre.find_by(id: params[:id])
    return head :not_found unless theatre

    render json: TheatrePostSelector.new(theatre:).weight_json
  end

  private

  def theatre_info_json(theatre, host_flg: nil, post_id: nil, post_started_at: nil, skipped: nil)
    host_flg = theatre.host_user_id == current_user&.id if host_flg.nil?
    post_id = theatre.current_post_id if post_id.nil?
    post_started_at = theatre.current_post_started_at if post_started_at.nil?

    json = { host_flg:,
             post_id:,
             post_started_at:,
             post_elapsed_ms: post_started_at ? ((Time.current - post_started_at) * 1000).floor : nil,
             watching_users: theatre.watching_users.as_json(only: [:id, :name]),
             skip_vote: skip_vote_status(theatre) }
    json[:skipped] = skipped unless skipped.nil?
    json
  end

  def skip_vote_status(theatre)
    watching_user_ids = theatre.watching_users.ids
    watching_users_count = watching_user_ids.size
    required_count = (watching_users_count / 2) + 1
    post = theatre.current_post
    votes =
        if post
          TheatreSkipVote.where(theatre:, post:, user_id: watching_user_ids)
        else
          TheatreSkipVote.none
        end

    { votes_count: post ? votes.count : 0,
      required_count:,
      watching_users_count:,
      voted: post && current_user ? votes.exists?(user_id: current_user.id) : false }
  end
end
