class ProjectsController < ApplicationController
  include ApplicationHelper
  include OperateProjectAbilityAble
  include ProjectsHelper
  include Acceleratorable

  before_action :require_login, except: %i[index branches branches_slice group_type_list simple show mp_show fork_users praise_users watch_users recommend banner_recommend about menu_list verify_auth_token]
  before_action :require_profile_completed, only: [:create, :migrate,:page_migrate,:verify_auth_token]
  before_action :load_repository, except: %i[index mp_show group_type_list migrate page_migrate create recommend banner_recommend verify_auth_token]
  before_action :authorizate_user_can_edit_project!, only: %i[update]
  before_action :project_public?, only: %i[fork_users praise_users watch_users]
  # before_action :request_limit, only: %i[index]

  def menu_list
    menu = []

    user_is_admin = current_user.admin? || @project.manager?(current_user)

    menu.append(menu_hash_by_name("home"))
    menu.append(menu_hash_by_name("code")) if @project.has_menu_permission("code")
    menu.append(menu_hash_by_name("issues")) if @project.has_menu_permission("issues")
    menu.append(menu_hash_by_name("pulls")) if @project.has_menu_permission("pulls") && @project.forge?
    menu.append(menu_hash_by_name("devops")) if @project.has_menu_permission("devops") && @project.forge?
    menu.append(menu_hash_by_name("versions")) if @project.has_menu_permission("versions")
    menu.append(menu_hash_by_name("wiki")) if @project.has_menu_permission("wiki") && @project.forge?
    menu.append(menu_hash_by_name("services")) if @project.has_menu_permission("services") && @project.forge? && (current_user.admin? || @project.member?(current_user.id))
    menu.append(menu_hash_by_name("resources")) if @project.has_menu_permission("resources") && @project.forge?
    menu.append(menu_hash_by_name("activity"))
    menu.append(menu_hash_by_name("settings")) if user_is_admin && @project.forge?
    menu.append(menu_hash_by_name("quit")) if !user_is_admin && @project.member(current_user.id) && @project.forge?

    render json: menu
  end

  def index
    scope = current_user.logged? ? Projects::ListQuery.call(params.merge(exclude_forked: "true"), current_user.id) : Projects::ListQuery.call(params.merge(exclude_forked: "true"))
    recommend_scope = current_user.logged? ? Projects::ListQuery.call(params.merge(recommend: "true"), current_user.id) : Projects::ListQuery.call(params.merge(recommend: "true"))
    forked_scope = current_user.logged? ? Projects::ListQuery.call(params.merge(exclude_forked: "false"), current_user.id) : Projects::ListQuery.call(params.merge(exclude_forked: "false"))
    scope = Project.from("( select * from ( #{ recommend_scope.reorder("recommend_index desc").to_sql} limit 10000000 ) as t1 UNION select * from ( #{ scope.to_sql} limit 10000000 ) as t2 UNION select * from ( #{ forked_scope.to_sql} limit 10000000 ) as t3  ) AS projects")
    @projects = kaminari_paginate(scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units))
    # @projects = paginate scope.includes(:project_category, :project_language, :repository, :project_educoder, :owner, :project_units)
    # @rand_projects = params[:page].to_i == 1 ? ([Project.find_by(id: 477)] | @projects) - [nil] : @projects
    @rand_projects = @projects
    category_id = params[:category_id]
    @total_count =
      if category_id.blank? && params[:search].blank? && params[:topic_id].blank? && params[:topic_name].blank?
        # 默认查询时count性能问题处理
        not_category_count = Project.where(project_category_id: nil).count
        ProjectCategory.sum("projects_count") - Project.visible.joins("left join organization_extensions on organization_extensions.organization_id = projects.user_id").where("organization_extensions.visibility =2").count + not_category_count
      elsif params[:search].present? || params[:topic_id].present? || params[:topic_name].present?
        @projects.total_count
      elsif params[:topic_id].present?
        topic = ProjectTopic.find_by(id: params[:topic_id])
        topic&.projects_count || 0
      else
        cate = ProjectCategory.find_by(id: category_id)
        cate&.projects_count || 0
      end
  end

  def create
    ActiveRecord::Base.transaction do
      Projects::CreateForm.new(project_params).validate!
      @project = Projects::CreateService.new(current_user, project_params).call
      # OpenProjectDevOpsJob.set(wait: 5.seconds).perform_later(@project&.id, current_user.id)
      UpdateProjectTopicJob.perform_later(@project.id) if @project.id.present?
    end
  rescue Gitea::Api::ServerError => ex
    uid_logger_error(ex.message)
    # tip_exception(ex.http_code, ex.message)
    tip_exception(ex.message)
  rescue ApplicationService::Error => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end

  def verify_auth_token
    data = Projects::VerifyAuthTokenService.call(params[:clone_addr], params[:auth_token])
    render_ok({data: data})
  end

  def migrate
    Projects::MigrateForm.new(mirror_params).validate!

    @project =
      if EduSetting.get("mirror_address").to_s.include?("github") && enable_accelerator?(mirror_params[:clone_addr])
        source_clone_url = mirror_params[:clone_addr]
        uid_logger("########## 已动加速器 ##########")
        hasProject = Gitea::Accelerator::GetService.call("root", mirror_params[:repository_name])
        if hasProject.present? && hasProject["original_url"] == source_clone_url
          uid_logger("########## 已在加速器查询到#{source_clone_url} ##########")
          Projects::MigrateService.call(current_user, mirror_params.merge(source_clone_url: source_clone_url, clone_addr: accelerator_url(mirror_params[:repository_name])))
        else
          result = Gitea::Accelerator::MigrateService.call(mirror_params)
          uid_logger("########## 已动加速器#{result} ###########{mirror_params}")
          if result[:status] == :success
            Rails.logger.info "########## 加速镜像成功 ########## "
            Projects::MigrateService.call(current_user,
                                          mirror_params.merge(source_clone_url: source_clone_url,
                                                              clone_addr: accelerator_url(mirror_params[:repository_name])))
          else
            Projects::MigrateService.call(current_user, mirror_params)
          end
        end
      elsif EduSetting.get("mirror_address").to_s.include?("cnpmjs") && mirror_params[:clone_addr].include?("github.com")
        source_clone_url = mirror_params[:clone_addr]
        clone_url = source_clone_url.gsub('github.com', 'github.com.cnpmjs.org')
        uid_logger("########## 更改clone_addr ##########")
        Projects::MigrateService.call(current_user, mirror_params.merge(source_clone_url: source_clone_url, clone_addr: clone_url))
      else
        Projects::MigrateService.call(current_user, mirror_params)
      end
  rescue Exception => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end


  def page_migrate
    return normal_status(-1, "您还未开通Page服务，无法进行新建站点") unless current_user.id_card_verify
    return normal_status(-1, "你已使用了 #{page_site_params[:identifier]} 作为page标识") if Page.exists?(identifier: page_site_params[:identifier], user: current_user)

    Projects::MigrateForm.new(mirror_params).validate!

    @project =
      if EduSetting.get("mirror_address").to_s.include?("github") && enable_accelerator?(mirror_params[:clone_addr])
        source_clone_url = mirror_params[:clone_addr]
        uid_logger("########## 已动加速器 ##########")
        result = Gitea::Accelerator::MigrateService.call(mirror_params)
        if result[:status] == :success
          Rails.logger.info "########## 加速镜像成功 ########## "
          Projects::MigrateService.call(current_user,
                                        mirror_params.merge(source_clone_url: source_clone_url,
                                                            clone_addr: accelerator_url(mirror_params[:repository_name])))
        else
          Projects::MigrateService.call(current_user, mirror_params)
        end
      elsif EduSetting.get("mirror_address").to_s.include?("cnpmjs") && mirror_params[:clone_addr].include?("github.com")
        source_clone_url = mirror_params[:clone_addr]
        clone_url = source_clone_url.gsub('github.com', 'github.com.cnpmjs.org')
        uid_logger("########## 更改clone_addr ##########")
        Projects::MigrateService.call(current_user, mirror_params.merge(source_clone_url: source_clone_url, clone_addr: clone_url))
      else
        Projects::MigrateService.call(current_user, mirror_params)
      end
    if @project.present?
      page = Page.new page_site_params
      page.user = current_user
      page.project = @project
    end

    if page.save
      render json: page
    end
  rescue Exception => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end

  def branches
    return @branches = [] unless @project.forge?

    # result = Gitea::Repository::Branches::ListService.call(@owner, @project.identifier)
    result = Gitea::Repository::Branches::ListNameService.call(@owner, @project.identifier, params[:name])
    @branches = result.is_a?(Hash) ? (result.key?(:status) ? [] : result) : result
  end

  def branches_slice
    return @branches = [] unless @project.forge?

    slice_result = Gitea::Repository::Branches::ListSliceService.call(@owner, @project.identifier)
    @branches_slice = slice_result.is_a?(Hash) && slice_result.key?(:status) ? [] : slice_result
  end

  def group_type_list
    # if current_user&.logged?
    #   projects = Project.list_user_projects(current_user.id)
    # else
    #   projects = Project.visible
    # end
    projects = Project.no_anomory_projects.secret_and_visible
    language_lists = projects.joins(:project_language).group("project_languages.name", "project_languages.id").size.sort_by{|i|-i[1]}.to_h.keys
    @project_group_list = language_lists.delete_if { |k, v| k.blank? }
  end

  def update
    ActiveRecord::Base.transaction do
      # TODO:
      # 临时特殊处理修改website、lesson_url操作方法
      if project_params.has_key?("website")
        if params[:project_topic_names].is_a?(Array)
          ProjectTopicRalate.where(project: @project).destroy_all
          params[:project_topic_names].each do |name|
            project_topic = ProjectTopic.find_or_create_by!(name: name.downcase)
            project_topic.project_topic_ralates.find_or_create_by!(project: @project)
          end
        end
        @project.update(project_params)
      elsif project_params.has_key?("default_branch")
        @project.update(project_params)
        gitea_params = {
          default_branch: @project.default_branch
        }
        Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
      elsif project_params.has_key?("has_actions")
        gitea_params = {
          has_actions: project_params[:has_actions]
        }
        Gitea::Repository::UpdateService.call(@owner, @project.identifier, gitea_params)
      else
        validate_params = project_params.slice(:name, :description,
                                               :project_category_id, :project_language_id, :private, :identifier)

        Projects::UpdateForm.new(validate_params.merge(user_id: @project.user_id, project_identifier: @project.identifier, project_name: @project.name)).validate!

        private = params[:private].nil? ? !@project.is_public : params[:private]
        private = @project.forked_from_project.present? ? !@project.forked_from_project.is_public : private

        new_project_params = project_params.except(:private).merge(is_public: !private)
        @project.update_attributes!(new_project_params)
        fork_pull_requests = PullRequest.where(fork_project_id: @project.id)
        if fork_pull_requests.present? 
          fork_pull_requests.update_all(fork_project_identifier: @project.identifier)
        end
        @project.forked_projects.map{|p| p.update!(is_public: @project.is_public)}
        gitea_params = {
          private: private,
          default_branch: @project.default_branch,
          website: @project.website,
          name: @project.identifier
        }
        gitea_repo = Gitea::Repository::UpdateService.call(@owner, @project&.repository&.identifier, gitea_params)
        @project.repository.update_attributes({ hidden: gitea_repo["private"], identifier: gitea_repo["name"] })
        # 更新对应所属分类下的项目数量（私有）
        before_is_public = @project.previous_changes[:is_public].present? ? @project.previous_changes[:is_public][0] : @project.is_public
        after_is_public = @project.previous_changes[:is_public].present? ? @project.previous_changes[:is_public][1] : @project.is_public
        before_pc_id = @project.previous_changes[:project_category_id].present? ? @project.previous_changes[:project_category_id][0] : @project.project_category_id
        after_pc_id = @project.previous_changes[:project_category_id].present? ? @project.previous_changes[:project_category_id][1] : @project.project_category_id
        before_pc = ProjectCategory.find_by_id(before_pc_id)
        after_pc = ProjectCategory.find_by_id(after_pc_id)
        before_pc.decrement!(:private_projects_count, 1) if before_pc.present? && !before_is_public
        after_pc.increment!(:private_projects_count, 1) if after_pc.present? && !after_is_public
      end
      SendTemplateMessageJob.perform_later('ProjectSettingChanged', current_user.id, @project&.id, @project.previous_changes.slice(:name, :description, :project_category_id, :project_language_id, :is_public, :identifier)) if Site.has_notice_menu?
    end
  rescue Exception => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end

  def show
  end

  def destroy
    if current_user.admin? || @project.manager?(current_user)
      ActiveRecord::Base.transaction do
        UserAction.create(action_id: @project.id, action_type: "DestroyProject", user_id: current_user.id, :ip => request.remote_ip, data_bank: @project.attributes.to_json)
        close_fork_pull_requests_by(@project)
        Gitea::Repository::DeleteService.new(@project.owner, @project.identifier,current_user.gitea_token).call
        @project.destroy!
        @project.forked_projects.update_all(forked_from_project_id: nil)
        # 如果该项目有所属的项目分类以及为私有项目，需要更新对应数量
        @project.project_category.decrement!(:private_projects_count, 1) if @project.project_category.present? && !@project.is_public
        render_ok
      end
    else
      render_forbidden('你没有权限操作')
    end
  rescue Exception => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end

  def quit
    user_is_admin = current_user.admin? || @project.manager?(current_user)
    if !user_is_admin && @project.member(current_user.id) && @project.forge?
      ActiveRecord::Base.transaction do
        Projects::DeleteMemberInteractor.call(@project.owner, @project, current_user)
        SendTemplateMessageJob.perform_later('ProjectMemberLeft', current_user.id, current_user.id, @project.id) if Site.has_notice_menu?
        render_ok
      end
    else
      render_forbidden('你不能退出该仓库')
    end
  rescue Exception => e
    uid_logger_error(e.message)
    tip_exception(e.message)
  end

  def watch_users
    start_at = params[:start_at]
    end_at = params[:end_at]
    watchers = @project.watchers.includes(:user).order("watchers.created_at desc").distinct
    watchers = watchers.where("watchers.created_at > ? and watchers.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
    @watchers_count = watchers.size
    @watchers = paginate(watchers)
  end

  def praise_users
    start_at = params[:start_at]
    end_at = params[:end_at]
    praises = @project.praise_treads.includes(:user).order("praise_treads.created_at desc").distinct
    praises = praises.where("praise_treads.created_at > ? and praise_treads.created_at < ?", Time.at(start_at.to_i), Time.at(end_at.to_i)) if start_at.present? && end_at.present?
    @praises_count = praises.size
    @praises = paginate(praises)
  end

  def fork_users
    fork_users = @project.fork_users.includes(:owner, :project, :fork_project).order("fork_users.created_at desc").distinct
    @forks_count = fork_users.size
    @fork_users = paginate(fork_users)
  end

  def user_actions
    user_actions = UserAction.where(action_type: "ViewProject-#{@project.id}").joins(:user)
    user_actions = user_actions.merge(User.like(params[:search]))
    @user_actions = kaminari_paginate(user_actions)
  end

  def simple
    if !@project.common? && @project&.repository&.mirror&.waiting?
      gitea_result = $gitea_client.get_repos_by_owner_repo(@project&.owner&.login, @project&.identifier) rescue nil
      if gitea_result.present? && !gitea_result["empty"]
        @project&.update_columns(gpid: gitea_result["id"])
        @project&.repository&.mirror&.succeeded!
        project_id = @project&.id
        user_id = @project&.owner&.id
        Rails.logger.info "############ mirror project_id,user_id: #{project_id},#{user_id}  ############"
        OpenProjectDevOpsJob.set(wait: 5.seconds).perform_later(project_id, user_id) if project_id.present? && user_id.present?
        UpdateProjectTopicJob.set(wait: 1.seconds).perform_later(project_id) if project_id.present?
        Rails.logger.info "############ mirror status: #{@project&.repository&.mirror&.status} ############"
      end
    elsif !@project.common? && @project&.repository&.mirror&.failed?
      # 导入失败的项目标记 project.status=0, 在列表中不显示
      @project&.update_columns(status: 0) if @project&.status == 1

      # Rails.logger.info "############ mirror status: #{@project&.repository&.mirror&.status}"
      # Gitea::Repository::DeleteService.new(@project.owner, @project.identifier,current_user.gitea_token).call
      # @project.destroy!
      # @project.forked_projects.update_all(forked_from_project_id: nil)
      # # 如果该项目有所属的项目分类以及为私有项目，需要更新对应数量
      # @project.project_category.decrement!(:private_projects_count, 1) if @project.project_category.present? && !@project.is_public
      # return render_error("导入失败，请重试！")
    end
    # 为了缓存活跃项目的基本信息，后续删除
    Cache::V2::ProjectCommonService.new(@project.id).read
    # 项目名称，标识，所有者变化时重置缓存
    project_common = $redis_cache.hgetall("v2-project-common:#{@project.id}")
    if project_common.present?
      if project_common["name"] != @project.name || project_common["identifier"] != @project.identifier || project_common["owner_id"] != @project.user_id
        Cache::V2::ProjectCommonService.new(@project.id).reset
      end
    end
    json_response(@project, current_user)
  end

  def recommend
    @projects = Project.recommend.includes(:repository, :project_category, :owner).order(visits: :desc)
  end

  def banner_recommend
    @projects = Project.recommend.where.not(recommend_index: 0).includes(:project_category, :owner, :project_language).order(recommend_index: :desc)
  end

  def about
    @project_detail = @project.project_detail
    @attachments = Array(@project_detail&.attachments) if request.get?
    ActiveRecord::Base.transaction do
      if request.post?
        require_login
        authorizate_user_can_edit_project!
        unless @project_detail.present?
          @project_detail = ProjectDetail.new(
            content: params[:content],
            project_id: @project.id)
        else
          @project_detail.content = params[:content]
        end
        if @project_detail.save!
          attachment_ids = Array(params[:attachment_ids])
          logger.info "=============> #{Array(params[:attachment_ids])}"
          @attachments = Attachment.where(id: attachment_ids).or(Attachment.where(uuid: attachment_ids))
          @attachments.update_all(
            container_id: @project_detail.id,
            container_type: @project_detail.model_name.name,
            author_id: current_user.id,
            description: "")
        end
      end
    end
  end

  def clone_detection
    forge_url = "#{Rails.application.config_for(:configuration)['platform_url']}"
    target_dir = "2-#{SecureRandom.hex(6)}"
    source_url = "#{forge_url}/#{@project.owner.name}/#{@project.identifier}.git"
    source_dir = "#{@project.id}-#{SecureRandom.hex(6)}"
    clone_dir = File.join(Rails.root, "public", "clone_detection")
    system("cd #{clone_dir} && git clone #{params[:target_url]} #{target_dir}")
    system("cd #{clone_dir} && git clone #{source_url} #{source_dir}")
    system("cd /var/www/NiCad-6.2 && ./nicad6cross functions java /var/www/forgeplus/public/clone_detection/#{source_dir} /var/www/forgeplus/public/clone_detection/#{target_dir}")
    view_url = "#{forge_url}/#{source_dir}_functions-blind-crossclones/#{source_dir}_functions-blind-crossclones-0.30-classes-withsource.html"
    system("rm -rf /var/www/forgeplus/public/clone_detection/#{source_dir}")
    system("rm -rf /var/www/forgeplus/public/clone_detection/#{target_dir}")
    render_ok({ view_url: view_url })
  rescue
    Rails.logger.info("clone_detection...............error，#{forge_url},#{source_url}")
    system("rm -rf /var/www/forgeplus/public/clone_detection/#{source_dir}")
    system("rm -rf /var/www/forgeplus/public/clone_detection/#{target_dir}")
  end

  private

  def project_params
    params.permit(:user_id, :name, :description, :repository_name, :website, :lesson_url, :default_branch, :identifier,
                  :project_category_id, :project_language_id, :license_id, :ignore_id, :private, :has_actions,
                  :blockchain, :blockchain_token_all, :blockchain_init_token, :pr_view_admin)
  end

  def mirror_params
    params.permit(:user_id, :name, :description, :repository_name, :is_mirror, :auth_username, :auth_token, :service,
                  :auth_password, :project_category_id, :project_language_id, :clone_addr, :private)
  end

  def page_site_params
    params.permit(:site_name, :identifier,:language_frame,:theme)
  end

  def project_public?
    return if @project.is_public?

    if current_user
      return if current_user.admin? || @project.member?(current_user.id)
      render_forbidden('你没有权限访问.')
    else
      render_unauthorized('你还未登录.')
    end
  end

  def close_fork_pull_requests_by(project)
    open_pull_requests = PullRequest.where(fork_project_id: project.id)
    if open_pull_requests.present?
      open_pull_requests.each do |pull_request|
        closed = PullRequests::CloseService.call(pull_request&.project.owner, pull_request&.project.repository, pull_request, current_user)
        if closed === true
          pull_request.project_trends.create!(user: current_user, project: pull_request&.project,action_type: ProjectTrend::CLOSE)
          # 合并请求下issue处理为关闭
          pull_request.issue&.update_attributes!({status_id:5})
          SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, pull_request.id) if Site.has_notice_menu?
        end
      end 
    end
  end
end
