class ArchivesSpaceService < Sinatra::Base

  # Job runners can specify permissions required to create or cancel
  # particular types of jobs, so we have special handling for it here

  def has_permissions_or_raise(job, permissions)
    runner = JobRunner.registered_runner_for(job['job']['jsonmodel_type'])

    runner.send(permissions).each do |perm|
      unless current_user.can?(perm)
        raise AccessDeniedException.new("Access denied")
      end
    end
  end


  def can_create_or_raise(job)
    has_permissions_or_raise(job, :create_permissions)
  end


  def can_cancel_or_raise(job)
    has_permissions_or_raise(job, :cancel_permissions)
  end


  Endpoint.post('/repositories/:repo_id/jobs')
    .description("Create a new job")
    .params(["job", JSONModel(:job), "The job object", :body => true],
            ["repo_id", :repo_id])
    .permissions([:create_job])
    .returns([200, :updated]) \
  do
    can_create_or_raise(params[:job])

    job = Job.create_from_json(params[:job], :user => current_user)

    created_response(job, params[:job])
  end


  Endpoint.post('/repositories/:repo_id/jobs_with_files')
    .description("Create a new job and post input files")
    .example("shell") do
      <<~SHELL
        curl -X POST \
        -H "X-ArchivesSpace-Session: $SESSION" \
        -H "Content-Type: multipart/form-data" \
        -F "files[0]=@FULL_LOCAL_PATH_TO_FILE" \
        -F "job={\"jsonmodel_type\": \"job\", \"status\": \"queued\", \"has_modified_records\": false, \"inactive_record\": false, \"job\": {\"jsonmodel_type\": \"import_job\", \"filenames\": [\"EAD_FILE_NAME\"], \"import_type\": \"ead_xml\"}};type=application/json" \
        http://localhost:8089/repositories/:repo_id/jobs_with_files

        # Note the files field needs to have a key to associate with the filename in 'filenames'
        # See https://github.com/archivesspace/archivesspace/issues/3671
      SHELL
    end
    .params(["job", JSONModel(:job)],
            ["files", [UploadFile]],
            ["repo_id", :repo_id])
    .no_data(true)
    .permissions([:create_job])
    .returns([200, :updated]) \
  do
    can_create_or_raise(params[:job])

    job = Job.create_from_json(params[:job], :user => current_user)

    params[:files].each do |file|
      job.add_file(file.tempfile)
    end

    created_response(job, params[:job])
  end


  Endpoint.get('/job_types')
    .description("List all supported job types")
    .params()
    .permissions([])
    .returns([200, "A list of supported job types"]) \
  do
    json_response(JobRunner.registered_job_types)
  end


  # This should probably be encapsulated somewhere
  # with other import-specific backend logic
  Endpoint.get('/repositories/:repo_id/jobs/import_types')
    .description("List all supported import job types")
    .params(["repo_id", :repo_id])
    .permissions([])
    .returns([200, "A list of supported import types"]) \
  do
    show_hidden = false
    json_response(Converter.list_import_types(show_hidden))
  end


  Endpoint.post('/repositories/:repo_id/jobs/:id/cancel')
    .description("Cancel a Job")
    .params(["id", :id],
            ["repo_id", :repo_id])
    .permissions([:cancel_job])
    .no_data(true)
    .returns([200, :updated]) \
  do
    can_cancel_or_raise(Job.to_jsonmodel(params[:id]))

    job = Job.get_or_die(params[:id])
    job.cancel!

    updated_response(job)
  end


  Endpoint.delete('/repositories/:repo_id/jobs/:id')
    .description("Delete a Job")
    .params(["id", :id],
            ["repo_id", :repo_id])
    .permissions([:cancel_job])
    .returns([200, :deleted]) \
  do
    handle_delete(Job, params[:id])
  end


  Endpoint.get('/repositories/:repo_id/jobs')
    .description("Get a list of Jobs for a Repository")
    .params(["repo_id", :repo_id])
    .paginated(true)
    .permissions([:view_repository])
    .returns([200, "[(:job)]"]) \
  do
    handle_listing(Job, params, {}, [:status, :id])
  end


  Endpoint.get('/repositories/:repo_id/jobs/active')
    .description("Get a list of all active Jobs for a Repository")
    .params(["resolve", :resolve],
            ["repo_id", :repo_id])
    .permissions([:view_repository])
    .returns([200, "[(:job)]"]) \
  do
    running = CrudHelpers.scoped_dataset(Job, :status => "running")
    queued = CrudHelpers.scoped_dataset(Job, :status => "queued")

    # Sort the running jobs newest to oldest, then show queued jobs oldest to
    # newest (since the oldest jobs run next)
    active = running.all.sort {|a, b| b.system_mtime <=> a.system_mtime} + queued.all.sort {|a, b| a.system_mtime <=> b.system_mtime}

    listing_response(active, Job)
  end


  Endpoint.get('/repositories/:repo_id/jobs/archived')
    .description("Get a list of all archived Jobs for a Repository")
    .params(["resolve", :resolve],
            ["repo_id", :repo_id])
    .permissions([:view_repository])
    .paginated(true)
    .returns([200, "[(:job)]"]) \
  do
    handle_listing(Job, params, Sequel.~(:status => ["running", "queued"]), Sequel.desc(:time_finished))
  end


  Endpoint.get('/repositories/:repo_id/jobs/:id')
    .description("Get a Job by ID")
    .params(["id", :id],
            ["resolve", :resolve],
            ["repo_id", :repo_id])
    .permissions([:view_repository])
    .returns([200, "(:job)"]) \
  do
    json_response(resolve_references(Job.to_jsonmodel(params[:id]), params[:resolve]))
  end


  Endpoint.get('/repositories/:repo_id/jobs/:id/log')
    .description("Get a Job's log by ID")
    .params(["id", :id],
            ["repo_id", :repo_id],
            ["offset",
             NonNegativeInteger,
             "The byte offset of the log file to show",
             :default => 0])
    .permissions([:view_repository])
    .returns([200, "The section of the import log between 'offset' and the end of file"]) \
  do
    job = Job.get_or_die(params[:id])
    (stream, length) = job.get_output_stream(params[:offset])

    [
     200,
     {'Content-Type' => 'text/plain', 'Content-Length' => length.to_s},
     Enumerator.new do |y|
       begin
         while (length > 0 && chunk = stream.read([length, 4096].min))
           y << chunk
           length -= chunk.bytesize
         end
       ensure
         stream.close
       end
     end
    ]
  end

  Endpoint.get('/repositories/:repo_id/jobs/:id/output_files')
    .description("Get a list of Job's output files by ID")
    .params(["id", :id],
            ["repo_id", :repo_id] )
    .permissions([:view_repository])
    .returns([200, "An array of output files"]) \
  do
    job = Job.get_or_die(params[:id])
    files = JobFile.filter( :job_id => job.id ).select(:id).map {|f| f[:id] }
    json_response(files)

  end

  Endpoint.get('/repositories/:repo_id/jobs/:id/output_files/:file_id')
  .description("Get a Job's output file by ID")
  .params(["id", :id],
          ["file_id", :id],
          ["repo_id", :repo_id] )
  .permissions([:view_repository])
  .returns([200, "Returns the file"]) \
do
  file = JobFile.filter(  :id => params[:file_id], :job_id => params[:id] ).select(:file_path).first
  # ANW-267: Windows will corrupt PDFs with DOS line endings unless we return the file as a binary.
  content_type 'application/octect-stream'
  IO.binread(file.full_file_path)
end

  Endpoint.get('/repositories/:repo_id/jobs/:id/records')
    .description("Get a Job's list of created URIs")
    .params(["id", :id],
            ["repo_id", :repo_id])
    .permissions([:view_repository])
    .paginated(true)
    .returns([200, "An array of created records"]) \
  do
    job = Job.get_or_die(params[:id])

    # Collection management records aren't true top-level records.  I think they
    # need a bit of a rethink.  They're really nested records, so they shouldn't
    # have URIs in the first place.
    handle_listing(JobCreatedRecord,
                   params,
                   Sequel.&(Sequel.~(Sequel.like(:record_uri, "%/collection_management/%")), {:job_id => job.id}),
                   Sequel.desc(:create_time))
  end

end
