# frozen_string_literal: true

module Navigator
  class BaseController < ApplicationController
    private

    def project_serializer(project, archived)
      {
        id: project.code,
        name: project.name,
        url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
        archived: project.archived,
        type: :project,
        has_children: project.has_children,
        children_url: navigator_project_path(project),
        disabled: project.disabled
      }
    end

    def folder_serializer(folder, archived)
      {
        id: folder.code,
        name: folder.name,
        url: project_folder_path(folder, view_mode: archived ? 'archived' : 'active'),
        archived: folder.archived,
        type: :folder,
        has_children: folder.try(:has_children),
        children_url: navigator_project_folder_path(folder)
      }
    end

    def experiment_serializer(experiment, archived)
      {
        id: experiment.code,
        name: experiment.name,
        url: my_modules_experiment_path(experiment, view_mode: archived ? 'archived' : 'active'),
        archived: experiment.archived_branch?,
        type: :experiment,
        has_children: experiment.has_children,
        children_url: navigator_experiment_path(experiment)
      }
    end

    def my_module_serializer(my_module, archived)
      {
        id: my_module.code,
        name: my_module.name,
        type: :my_module,
        url: protocols_my_module_path(my_module, view_mode: archived ? 'archived' : 'active'),
        archived: my_module.archived_branch?,
        has_children: false
      }
    end

    def fetch_projects(object = nil, archived = false)
      if object.is_a?(ProjectFolder)
        folder = object
        project = nil
      else
        folder = object&.project_folder
        project = object
      end
      has_children_sql = if !archived
                           'SUM(CASE WHEN experiments.archived IS FALSE OR
                                          (projects.archived IS TRUE AND experiments.id IS  NOT NULL)
                                     THEN 1 ELSE 0 END) > 0 AS has_children'
                         else
                           'SUM(CASE WHEN experiments.archived IS TRUE OR
                                          my_modules.archived IS TRUE OR
                                          (projects.archived IS TRUE AND experiments.id IS  NOT NULL)
                                     THEN 1 ELSE 0 END) > 0 AS has_children'
                         end
      disabled_sql = 'SUM(CASE
                          WHEN project_user_roles IS NULL AND
                               project_user_group_roles IS NULL AND
                               project_team_roles IS NULL
                          THEN 0 ELSE 1 END) < 1 AS disabled'

      projects =
        if can_manage_team?(current_team)
          # Team owners see all projects in the team
          current_team.projects
        else
          current_team.projects.readable_by_user(current_user, current_team)
        end

      projects.where(project_folder_id: folder)
              .with_children_viewable_by_user(current_user)
              .joins("LEFT OUTER JOIN user_assignments project_user_assignments
                        ON project_user_assignments.assignable_type = 'Project'
                        AND project_user_assignments.assignable_id = projects.id
                        AND project_user_assignments.user_id = #{current_user.id}
                      LEFT OUTER JOIN user_roles project_user_roles
                        ON project_user_roles.id = project_user_assignments.user_role_id
                        AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]
                      LEFT OUTER JOIN user_group_assignments project_user_group_assignments
                        ON project_user_group_assignments.assignable_type = 'Project'
                        AND project_user_group_assignments.assignable_id = projects.id
                        AND project_user_group_assignments.user_group_id IN (
                          SELECT user_group_memberships.user_group_id FROM user_group_memberships
                          WHERE user_group_memberships.user_id = #{current_user.id}
                        )
                      LEFT OUTER JOIN user_roles project_user_group_roles
                        ON project_user_group_roles.id = project_user_group_assignments.user_role_id
                        AND project_user_group_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]
                      LEFT OUTER JOIN team_assignments project_team_assignments
                        ON project_team_assignments.assignable_type = 'Project'
                        AND project_team_assignments.assignable_id = projects.id
                        AND project_team_assignments.team_id = #{current_team.id}
                      LEFT OUTER JOIN user_roles project_team_roles
                        ON project_team_roles.id = project_team_assignments.user_role_id
                        AND project_team_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]
              ")
              .where('projects.archived = :archived OR
                      (
                        (
                          experiments.archived = :archived OR
                          my_modules.archived = :archived
                        ) AND
                        :archived IS TRUE
                      ) OR
                      projects.id = :project_id',
                     archived: archived,
                     project_id: project&.id || -1)
              .select(
                'projects.id',
                'projects.name',
                'projects.archived',
                disabled_sql,
                has_children_sql
              ).group('projects.id')
    end

    def fetch_project_folders(object = nil, archived = false)
      folder = if object.is_a?(ProjectFolder)
                 object
               else
                 object&.project_folder
               end
      has_children_sql = 'SUM(CASE WHEN visible_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL
      THEN 1 ELSE 0 END) > 0'
      visible_projects =
        if can_manage_team?(current_team)
          # Team owners see all projects in the team
          current_team.projects
        else
          current_team.projects.readable_by_user(current_user, current_team)
        end
      current_team.project_folders.where(parent_folder: folder)
                  .left_outer_joins(:projects, project_folders: {})
                  .joins(
                    "LEFT OUTER JOIN (#{visible_projects.where(archived: archived).to_sql}) " \
                    "visible_projects ON visible_projects.project_folder_id = project_folders.id"
                  )
                  .select(
                    'project_folders.id',
                    'project_folders.name',
                    'project_folders.archived',
                    "#{has_children_sql} AS has_children"
                  ).group('project_folders.id')
                  .having('project_folders.archived = :archived', archived: archived)
    end

    def fetch_experiments(object, archived = false)
      if object.is_a?(Project)
        project = object
        experiment = nil
      else
        project = object.project
        experiment = object
      end

      has_children_sql = if !archived
                           'SUM(CASE WHEN my_modules.archived IS FALSE OR
                                          (experiments.archived IS TRUE AND my_modules.id IS NOT NULL)
                                     THEN 1 ELSE 0 END) > 0 AS has_children'
                         elsif project.archived?
                           'COUNT(my_modules.id) > 0 AS has_children'
                         else
                           'SUM(CASE WHEN my_modules.archived IS TRUE OR
                                          (experiments.archived IS TRUE AND my_modules.id IS NOT NULL)
                                     THEN 1 ELSE 0 END) > 0 AS has_children'
                         end
      experiments = project.experiments
                           .readable_by_user(current_user, current_team)
                           .with_children_viewable_by_user(current_user)
                           .select(
                             'experiments.id',
                             'experiments.name',
                             'experiments.archived',
                             'experiments.project_id',
                             has_children_sql
                           ).group('experiments.id')
      unless project.archived?
        experiments = experiments.where('
            experiments.archived = :archived OR
            my_modules.archived = :archived AND
            :archived IS TRUE OR
            experiments.id = :experiment_id
          ', archived: archived, experiment_id: experiment&.id || -1)
      end

      experiments
    end

    def fetch_my_modules(experiment, archived = false)
      my_modules = experiment.my_modules.readable_by_user(current_user, current_team)
      my_modules = my_modules.where(archived: archived) unless experiment.archived_branch?

      my_modules
    end

    def build_folder_tree(folder, children)
      archived = params[:archived] == 'true'
      tree = fetch_projects(folder.parent_folder, archived).map { |i| project_serializer(i, archived) } +
             fetch_project_folders(folder.parent_folder, archived).map { |i| folder_serializer(i, archived) }
      # Tree will not contain folder when folder archived state and params archived values are contradictory
      tree.find { |i| i[:id] == folder.code } || (tree << folder_serializer(folder, archived))
      tree.find { |i| i[:id] == folder.code }[:children] = children
      tree = build_folder_tree(folder.parent_folder, tree) if folder.parent_folder.present?
      tree
    end

    def project_level_branch(object = nil)
      archived = params[:archived] == 'true'
      fetch_projects(object, archived)
        .map { |i| project_serializer(i, archived) } +
        fetch_project_folders(object, archived)
        .map { |i| folder_serializer(i, archived) }
    end

    def experiment_level_branch(object)
      archived = params[:archived] == 'true'
      fetch_experiments(object, archived)
        .map { |i| experiment_serializer(i, archived) }
    end

    def my_module_level_branch(experiment)
      archived = params[:archived] == 'true'
      fetch_my_modules(experiment, archived)
        .map { |i| my_module_serializer(i, archived) }
    end
  end
end
