package com.google.job.data;

import com.google.api.core.ApiFunction;
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableSet;
import com.google.cloud.firestore.*;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.utils.FireStoreUtils;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;

/** Helps persist and retrieve job posts. */
public final class JobsDatabase {
    private static final String JOB_COLLECTION = "Jobs";
    private static final String JOB_STATUS_FIELD = "jobStatus";

    /**
     * Adds a newly created job post.
     *
     * @param newJob Newly created job post. Assumes that it is non-nullable.
     * @return A future of the detailed information of the writing.
     */
    public Future<WriteResult> addJob(Job newJob) throws IOException {
        // Add document data after generating an id
        DocumentReference addedDocRef = FireStoreUtils.getFireStore()
                .collection(JOB_COLLECTION).document();

        String jobId = addedDocRef.getId();

        // Updates the Job with cloud firestore id
        // Status is already set when parsing the job post
        Job job = newJob.toBuilder()
                .setJobId(jobId)
                .build();

        return addedDocRef.set(job);
    }

    /**
     * Edits the job post.
     *
     * @param jobId Id for the target job post in the database.
     * @param updatedJob Updated job post.
     * @return A future of document reference for the updated job post.
     * @throws IllegalArgumentException If the job id is invalid.
     */
    public Future<DocumentReference> setJob(String jobId, Job updatedJob) throws IllegalArgumentException, IOException {
        if (jobId.isEmpty()) {
            throw new IllegalArgumentException("Job Id should be an non-empty string");
        }
        
        // Sets the Job with cloud firestore id and ACTIVE status
        Job job = updatedJob.toBuilder()
                .setJobId(jobId)
                .build();

        // Runs an asynchronous transaction
        ApiFuture<DocumentReference> futureTransaction = FireStoreUtils.getFireStore().runTransaction(transaction -> {
            final DocumentReference documentReference = FireStoreUtils.getFireStore()
                    .collection(JOB_COLLECTION).document(jobId);

            // Verifies if the current user can update the job post with this job id
            // TODO(issue/25): incorporate the account stuff into job post.
            DocumentSnapshot documentSnapshot = transaction.get(documentReference).get();

            // Job does not exist
            if (!documentSnapshot.exists()) {
                throw new IllegalArgumentException("Invalid jobId");
            }

            // Overwrites the whole job post
            transaction.set(documentReference, job);

            return documentReference;
        });
        
        return futureTransaction;
    }

    /**
     * Marks a job post as DELETED.
     *
     * @param jobId Cloud Firestore Id of the job post.
     * @return A future of document reference for the updated job post.
     */
    public Future<DocumentReference> markJobPostAsDeleted(String jobId) throws IOException {
        if (jobId.isEmpty()) {
            throw new IllegalArgumentException("Job Id should be an non-empty string");
        }
        
        // Runs an asynchronous transaction
        ApiFuture<DocumentReference> futureTransaction = FireStoreUtils.getFireStore().runTransaction(transaction -> {
            final DocumentReference documentReference = FireStoreUtils.getFireStore()
                    .collection(JOB_COLLECTION).document(jobId);

            // Verifies if the current user can update the job post with this job id
            // TODO(issue/25): incorporate the account stuff into job post.
            DocumentSnapshot documentSnapshot = transaction.get(documentReference).get();

            // Job does not exist
            if (!documentSnapshot.exists()) {
                throw new IllegalArgumentException("Invalid jobId");
            }

            // Updates the jobStatus field to DELETED
            transaction.update(documentReference, JOB_STATUS_FIELD, JobStatus.DELETED);

            return documentReference;
        });

        return futureTransaction;
    }

    /**
     * Fetches the snapshot future of a specific job post.
     *
     * @param jobId Id for the job post in the database.
     * @return Future of the target job post.
     * @throws IllegalArgumentException If the job id is invalid.
     */
    public Future<Optional<Job>> fetchJob(String jobId) throws IllegalArgumentException, IOException {
        DocumentReference docRef = FireStoreUtils.getFireStore()
                .collection(JOB_COLLECTION).document(jobId);

        // Asynchronously retrieves the document
        ApiFuture<DocumentSnapshot> snapshotFuture = docRef.get();

        ApiFunction<DocumentSnapshot, Optional<Job>> jobFunction = new ApiFunction<DocumentSnapshot, Optional<Job>>() {
            @NullableDecl
            public Optional<Job> apply(@NullableDecl DocumentSnapshot documentSnapshot) {
                return FireStoreUtils.convertDocumentSnapshotToPOJO(documentSnapshot, Job.class);
            }
        };

        return ApiFutures.transform(snapshotFuture, jobFunction, MoreExecutors.directExecutor());
    }

    /** Returns future of all ACTIVE and eligible job posts in database. */
    public Future<Collection<Job>> fetchAllEligibleJobs(List<String> skills) throws IOException {

        final Query activeJobsQuery = FireStoreUtils.getFireStore()
                .collection(JOB_COLLECTION)
                .whereEqualTo(JOB_STATUS_FIELD, JobStatus.ACTIVE);

        ApiFuture<QuerySnapshot> querySnapshotFuture = activeJobsQuery.get();

        ApiFunction<QuerySnapshot, Collection<Job>> function = documents -> {
            ImmutableSet.Builder<Job> jobs = ImmutableSet.builder();

            for (DocumentSnapshot document : documents) {
                Job job = document.toObject(Job.class);
                List<String> requirements = job.getRequirements();

                // Eligible job: all job requirements are contained in the applicant skills.
                if (skills.containsAll(requirements)) {
                    jobs.add(job);
                }
            }

            return jobs.build();
        };

        return ApiFutures.transform(querySnapshotFuture, function, MoreExecutors.directExecutor());

//        // Runs an asynchronous transaction
//        ApiFuture<Collection<Job>> futureTransaction = FireStoreUtils.getFireStore().runTransaction(transaction -> {
//            ImmutableSet.Builder<Job> jobs = ImmutableSet.builder();
//
//            final Query activeJobsQuery = FireStoreUtils.getFireStore()
//                .collection(JOB_COLLECTION)
//                .whereEqualTo(JOB_STATUS_FIELD, JobStatus.ACTIVE);
//
//            List<QueryDocumentSnapshot> documents = transaction.get(activeJobsQuery).get().getDocuments();
//
//            for (DocumentSnapshot document : documents) {
//                Job job = document.toObject(Job.class);
//                List<String> requirements = job.getRequirements();
//
//                // Eligible job: all job requirements are contained in the applicant skills.
//                if (skills.containsAll(requirements)) {
//                    jobs.add(job);
//                }
//            }
//
//            return jobs.build();
//        });
//
//        return futureTransaction;
    }
}

@@ -6748,1059 +6748,8 @@
 ());
-%0A%0A//        // Runs an asynchronous transaction%0A//        ApiFuture%3CCollection%3CJob%3E%3E futureTransaction = FireStoreUtils.getFireStore().runTransaction(transaction -%3E %7B%0A//            ImmutableSet.Builder%3CJob%3E jobs = ImmutableSet.builder();%0A//%0A//            final Query activeJobsQuery = FireStoreUtils.getFireStore()%0A//                .collection(JOB_COLLECTION)%0A//                .whereEqualTo(JOB_STATUS_FIELD, JobStatus.ACTIVE);%0A//%0A//            List%3CQueryDocumentSnapshot%3E documents = transaction.get(activeJobsQuery).get().getDocuments();%0A//%0A//            for (DocumentSnapshot document : documents) %7B%0A//                Job job = document.toObject(Job.class);%0A//                List%3CString%3E requirements = job.getRequirements();%0A//%0A//                // Eligible job: all job requirements are contained in the applicant skills.%0A//                if (skills.containsAll(requirements)) %7B%0A//                    jobs.add(job);%0A//                %7D%0A//            %7D%0A//%0A//            return jobs.build();%0A//        %7D);%0A//%0A//        return futureTransaction;
 %0A   

Delete useless comment