/*
* RHQ Management Platform
* Copyright (C) 2009 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.enterprise.server.plugin.pc.content;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.transaction.TransactionManager;

import org.testng.annotations.Test;

import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.content.ContentSource;
import org.rhq.core.domain.content.ContentSourceType;
import org.rhq.core.domain.content.PackageVersionContentSource;
import org.rhq.core.domain.content.Repo;
import org.rhq.core.domain.content.RepoGroup;
import org.rhq.core.domain.content.RepoRelationship;
import org.rhq.core.domain.content.RepoRepoGroup;
import org.rhq.core.domain.content.RepoRepoRelationship;
import org.rhq.core.domain.criteria.RepoCriteria;
import org.rhq.core.domain.util.PageList;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.content.ContentSourceManagerLocal;
import org.rhq.enterprise.server.content.RepoManagerLocal;
import org.rhq.enterprise.server.test.AbstractEJB3Test;
import org.rhq.enterprise.server.util.LookupUtil;

/**
 * @author Jason Dobies
 */
public class ContentProviderManagerSyncContentProviderTest extends AbstractEJB3Test {

    private static final String CUSTOM_IMPORTED_REPO_NAME = "customImportedRepo";

    private static final String PREVIOUS_CANDIDATE_REPO_NAME = "testPreviousCandidate";

    private TestContentServerPluginService pluginService;

    // The following variables need to be cleaned up at the end of the test

    private ContentSourceType testSourceType;

    private ContentSource syncSource; // Source that is syncced in this test
    private ContentSource nonSyncSource; // Existing source NOT being syncced in this test

    private Repo nonCandidateOnOtherSource; // Should NOT appear in the sync packages call since its a different source

    private List<Integer> reposToDelete = new ArrayList<Integer>();
    private List<Integer> repoGroupsToDelete = new ArrayList<Integer>();
    private Integer repoId;
    private Integer relatedRepoId;

    @Override
    protected void beforeMethod() throws Exception {

        // Plugin service setup
        prepareScheduler();
        pluginService = new TestContentServerPluginService(this);

        // Because of the (current) transaction settings of some of the nested methods (i.e. REQUIRES_NEW),
        // this test must commit its data and clean up after itself, as compared to simply rolling back the
        // transaction at the end.
        TransactionManager tx = getTransactionManager();
        tx.begin();
        EntityManager entityManager = getEntityManager();

        ContentSourceManagerLocal contentManager = LookupUtil.getContentSourceManager();
        RepoManagerLocal repoManager = LookupUtil.getRepoManagerLocal();
        SubjectManagerLocal subjectManager = LookupUtil.getSubjectManager();
        Subject overlord = subjectManager.getOverlord();

        // Create a sample content source type that will be used in this test
        testSourceType = new ContentSourceType("testType");
        entityManager.persist(testSourceType);
        entityManager.flush();
        
        //need to actually commit the tx so that the subsequent code can see the new entry in 
        //the database
        tx.commit();
        
        tx.begin();
        //new entity manager associated with the new tx
        entityManager = getEntityManager();
        
        // Add a content source to sync in this test
        syncSource = new ContentSource("testSource1", testSourceType);
        contentManager.simpleCreateContentSource(overlord, syncSource);
        entityManager.flush();

        // Add an extra content source that isn't being syncced
        nonSyncSource = new ContentSource("testSource2", testSourceType);
        contentManager.simpleCreateContentSource(overlord, nonSyncSource);
        entityManager.flush();

        // Add existing repo against other source (this shouldn't show up in the request for packages)
        nonCandidateOnOtherSource = new Repo("nonCandidateOnOtherSource");
        nonCandidateOnOtherSource.setCandidate(false);
        nonCandidateOnOtherSource.addContentSource(nonSyncSource);
        repoManager.createRepo(overlord, nonCandidateOnOtherSource);

        tx.commit();
    }

    @Override
    protected void afterMethod() throws Exception {

        try {
            // Transactional stuff
            TransactionManager tx = getTransactionManager();
            tx.begin();
            EntityManager entityManager = getEntityManager();

            RepoManagerLocal repoManager = LookupUtil.getRepoManagerLocal();
            SubjectManagerLocal subjectManager = LookupUtil.getSubjectManager();
            Subject overlord = subjectManager.getOverlord();

            // Delete the repo relationships
            entityManager.createNamedQuery(RepoRepoRelationship.DELETE_BY_REPO_ID).setParameter("repoId", repoId)
                .executeUpdate();

            entityManager.createNamedQuery(RepoRelationship.DELETE_BY_RELATED_REPO_ID)
                .setParameter("relatedRepoId", relatedRepoId).executeUpdate();

            // Delete any repos that were created in this test
            for (Integer repoId : reposToDelete) {
                repoManager.deleteRepo(overlord, repoId);
            }
            reposToDelete.clear();

            // Delete any repo groups that were created in this test
            for (Integer repoGroupId : repoGroupsToDelete) {
                repoManager.deleteRepoGroup(overlord, repoGroupId);
            }
            repoGroupsToDelete.clear();

            // First disassociate packages from the content source
            entityManager.createNamedQuery(PackageVersionContentSource.DELETE_BY_CONTENT_SOURCE_ID)
                .setParameter("contentSourceId", syncSource.getId()).executeUpdate();

            // Delete the existing repos
            nonCandidateOnOtherSource = entityManager.find(Repo.class, nonCandidateOnOtherSource.getId());
            entityManager.remove(nonCandidateOnOtherSource);

            // Delete the source that was created
            syncSource = entityManager.find(ContentSource.class, syncSource.getId());
            entityManager.remove(syncSource);

            nonSyncSource = entityManager.find(ContentSource.class, nonSyncSource.getId());
            entityManager.remove(nonSyncSource);

            // Delete the fake source type
            testSourceType = entityManager.find(ContentSourceType.class, testSourceType.getId());
            entityManager.remove(testSourceType);

            tx.commit();
        } finally {
            // Plugin service teardown
            unprepareServerPluginService();
            unprepareScheduler();
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void synchronizeContentSource() throws Exception {

        try {
            getTransactionManager().begin();

            // Setup
            // --------------------------------------------
            RepoManagerLocal repoManager = LookupUtil.getRepoManagerLocal();
            SubjectManagerLocal subjectManager = LookupUtil.getSubjectManager();
            Subject overlord = subjectManager.getOverlord();

            // -> Add an already imported repo to the system so it already exists when the report introduces it
            Repo existingImportedRepo = new Repo(TestContentProvider.EXISTING_IMPORTED_REPO_NAME);
            existingImportedRepo.setCandidate(false);
            repoManager.createRepo(overlord, existingImportedRepo);

            // -> Add an already imported repo that wasn't introduced from the report; a user created repo
            Repo customImportedRepo = new Repo(CUSTOM_IMPORTED_REPO_NAME);
            customImportedRepo.setCandidate(false);
            customImportedRepo.addContentSource(syncSource);
            repoManager.createRepo(overlord, customImportedRepo);

            // -> Simulate a candidate repo from a previous import that will be in this report as well
            Repo existingCandidateRepo = new Repo(TestContentProvider.EXISTING_CANDIDATE_REPO_NAME);
            existingCandidateRepo.setCandidate(true);
            repoManager.createRepo(overlord, existingCandidateRepo);

            // -> Simulate a candidate repo from a previous import that will *NOT* be in this report
            Repo previousRepo = new Repo(PREVIOUS_CANDIDATE_REPO_NAME);
            previousRepo.setCandidate(true);
            previousRepo.addContentSource(syncSource);
            repoManager.createRepo(overlord, previousRepo);

            // Test
            // --------------------------------------------
            // TestContentProviderManager providerManager = new TestContentProviderManager();
            pluginService.getContentProviderManager().testConnection(syncSource.getId());

            boolean completed = pluginService.getContentProviderManager()
                .synchronizeContentProvider(syncSource.getId());
            assert completed;

            // Verify RepoGroups
            RepoGroup repoGroup = repoManager.getRepoGroupByName("testRepoGroup");
            assert repoGroup != null;
            repoGroupsToDelete.add(repoGroup.getId());

            // Verify Repos
            // --------------------------------------------
            List<Repo> retrievedRepos;

            // -> Simple Repo
            retrievedRepos = repoManager.getRepoByName("testRepo1");
            assert retrievedRepos.size() == 1;
            assert retrievedRepos.get(0).isCandidate();
            reposToDelete.add(retrievedRepos.get(0).getId());

            // -> Repo in a group
            retrievedRepos = repoManager.getRepoByName("testRepo2");
            assert retrievedRepos.size() == 1;
            assert retrievedRepos.get(0).isCandidate();
            reposToDelete.add(retrievedRepos.get(0).getId());

            Repo repoInGroup = retrievedRepos.get(0);

            RepoCriteria findWithRepoGroup = new RepoCriteria();
            findWithRepoGroup.fetchRepoRepoGroups(true);
            findWithRepoGroup.addFilterId(repoInGroup.getId());

            PageList<Repo> repoPageList = repoManager.findReposByCriteria(overlord, findWithRepoGroup);
            repoInGroup = repoPageList.get(0);

            Set<RepoRepoGroup> repoGroups = repoInGroup.getRepoRepoGroups();
            assert repoGroups.size() == 1;

            RepoRepoGroup repoRepoGroup = repoGroups.iterator().next();
            assert repoRepoGroup.getRepoRepoGroupPK().getRepoGroup().getName().equals("testRepoGroup");
            assert repoRepoGroup.getRepoRepoGroupPK().getRepo().getName().equals("testRepo2");

            // -> Repo with a parent
            retrievedRepos = repoManager.getRepoByName("testRepo3");
            assert retrievedRepos.size() == 1;
            assert retrievedRepos.get(0).isCandidate();
            reposToDelete.add(retrievedRepos.get(0).getId());
            relatedRepoId = retrievedRepos.get(0).getId();

            retrievedRepos = repoManager.getRepoByName("testRepo4");
            assert retrievedRepos.size() == 1;
            assert retrievedRepos.get(0).isCandidate();
            reposToDelete.add(retrievedRepos.get(0).getId());
            repoId = retrievedRepos.get(0).getId();

            RepoCriteria findWithRelationships = new RepoCriteria();
            findWithRelationships.addFilterName("testRepo4");
            findWithRelationships.fetchRepoRepoRelationships(true);
            PageList<Repo> childRepoList = repoManager.findReposByCriteria(overlord, findWithRelationships);
            assert childRepoList.size() == 1;

            Repo childRepo = childRepoList.get(0);
            Set<RepoRepoRelationship> childRepoRepoRelationship = childRepo.getRepoRepoRelationships();
            assert childRepoRepoRelationship.size() == 1;

            // -> Repo that was already imported in the system (make sure there is still only one)
            retrievedRepos = repoManager.getRepoByName(TestContentProvider.EXISTING_IMPORTED_REPO_NAME);
            assert retrievedRepos.size() == 1;
            reposToDelete.add(retrievedRepos.get(0).getId());

            // -> Repo that was imported but not in the report (i.e. a user created repo)
            retrievedRepos = repoManager.getRepoByName(CUSTOM_IMPORTED_REPO_NAME);
            assert retrievedRepos.size() == 1;
            reposToDelete.add(retrievedRepos.get(0).getId());

            // -> Repo that was already a candidate in the system (make sure it's not added again)
            retrievedRepos = repoManager.getRepoByName(TestContentProvider.EXISTING_CANDIDATE_REPO_NAME);
            assert retrievedRepos.size() == 1;
            reposToDelete.add(retrievedRepos.get(0).getId());

            // -> Make sure a repo that was previously a candidate of this content source but did not
            //    come back in the latest sync is removed
            retrievedRepos = repoManager.getRepoByName(PREVIOUS_CANDIDATE_REPO_NAME);
            assert retrievedRepos.size() == 0;

            // -> Non-existent repo
            retrievedRepos = repoManager.getRepoByName("testRepoFoo");
            assert retrievedRepos.size() == 0;

            getTransactionManager().commit();
        } catch (Throwable t) {
            getTransactionManager().rollback();
        }
    }
}
