/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.ivy.core.cache;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Date;

import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.DownloadStatus;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
import org.apache.ivy.plugins.repository.BasicResource;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.ResourceDownloader;
import org.apache.ivy.plugins.repository.url.URLResource;
import org.apache.ivy.plugins.resolver.MockResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.DefaultMessageLogger;
import org.apache.ivy.util.Message;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Delete;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

/**
 * @see DefaultResolutionCacheManager
 */
public class DefaultRepositoryCacheManagerTest {

    private DefaultRepositoryCacheManager cacheManager;

    private Artifact artifact;

    private ArtifactOrigin origin;

    private Ivy ivy;

    @Before
    public void setUp() throws Exception {
        File f = File.createTempFile("ivycache", ".dir");
        ivy = new Ivy();
        ivy.configureDefault();
        ivy.getLoggerEngine().setDefaultLogger(new DefaultMessageLogger(Message.MSG_DEBUG));
        IvyContext.pushNewContext().setIvy(ivy);

        IvySettings settings = ivy.getSettings();
        f.delete(); // we want to use the file as a directory, so we delete the file itself
        cacheManager = new DefaultRepositoryCacheManager();
        cacheManager.setSettings(settings);
        cacheManager.setBasedir(f);

        artifact = createArtifact("org", "module", "rev", "name", "type", "ext");

        Artifact originArtifact = createArtifact("org", "module", "rev", "name", "pom.original",
            "pom");
        origin = new ArtifactOrigin(originArtifact, true, "file:/some/where.pom");

        cacheManager.saveArtifactOrigin(originArtifact, origin);
        cacheManager.saveArtifactOrigin(artifact, origin);
    }

    @After
    public void tearDown() {
        IvyContext.popContext();
        Delete del = new Delete();
        del.setProject(new Project());
        del.setDir(cacheManager.getRepositoryCacheRoot());
        del.execute();
    }

    @Test
    public void testArtifactOrigin() {
        ArtifactOrigin found = cacheManager.getSavedArtifactOrigin(artifact);
        assertEquals(origin, found);
        assertEquals("pom", found.getArtifact().getExt());

        artifact = createArtifact("org", "module", "rev", "name", "type2", "ext");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));
    }

    @Test
    public void testUniqueness() {
        cacheManager.saveArtifactOrigin(artifact, origin);

        artifact = createArtifact("org1", "module", "rev", "name", "type", "ext");
        ArtifactOrigin found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));

        artifact = createArtifact("org", "module1", "rev", "name", "type", "ext");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));

        artifact = createArtifact("org", "module", "rev1", "name", "type", "ext");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));

        artifact = createArtifact("org", "module", "rev", "name1", "type", "ext");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));

        artifact = createArtifact("org", "module", "rev", "name", "type1", "ext");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));

        artifact = createArtifact("org", "module", "rev", "name", "type", "ext1");
        found = cacheManager.getSavedArtifactOrigin(artifact);
        assertTrue(ArtifactOrigin.isUnknown(found));
    }

    @Test
    public void wontWritePropertiesOutsideOfCache() {
        cacheManager.setDataFilePattern("a/../../../../../../");
        try {
            cacheManager.saveArtifactOrigin(artifact, origin);
            fail("expected an exception");
        } catch (IllegalArgumentException ex) {
            // expected
        }

        ModuleId mi = new ModuleId("org", "module");
        ModuleRevisionId mridLatest = new ModuleRevisionId(mi, "trunk", "latest.integration");
        try {
            cacheManager.saveResolvedRevision("resolver1", mridLatest, "1.1");
            fail("expected an exception");
        } catch (IllegalArgumentException ex) {
            // expected
        }
    }

    @Test
    public void wontDownloadOutsideOfCache() throws Exception {
        DefaultRepositoryCacheManager mgr = new DefaultRepositoryCacheManager() {
            {
                setUseOrigin(false);
                setSettings(ivy.getSettings());
                setBasedir(cacheManager.getBasedir());
            }

            @Override
            public String getArchivePathInCache(Artifact artifact, ArtifactOrigin origin) {
                return "../foo.txt";
            }
        };

        ArtifactResourceResolver resolver = new ArtifactResourceResolver() {
            @Override
            public ResolvedResource resolve(Artifact artifact) {
                try {
                    return new ResolvedResource(new URLResource(new URL("https://ant.apache.org/")), "latest");
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        };

        ArtifactDownloadReport report = mgr.download(artifact, resolver, null, new CacheDownloadOptions());
        assertEquals(DownloadStatus.FAILED, report.getDownloadStatus());
        assertTrue(report.getDownloadDetails().contains("is outside"));
    }

    @Test
    @Ignore
    public void testLatestIntegrationIsCachedPerResolver() throws Exception {
        // given a module org#module
        ModuleId mi = new ModuleId("org", "module");

        // and a latest.integration mrid/dd
        ModuleRevisionId mridLatest = new ModuleRevisionId(mi, "trunk", "latest.integration");
        DependencyDescriptor ddLatest = new DefaultDependencyDescriptor(mridLatest, false);

        // and some random options
        CacheMetadataOptions options = new CacheMetadataOptions().setCheckTTL(false);

        // setup resolver1 to download the static content so we can call cacheModuleDescriptor
        MockResolver resolver = new MockResolver();
        resolver.setName("resolver1");
        resolver.setSettings(ivy.getSettings());
        ivy.getSettings().addResolver(resolver);
        ResourceDownloader downloader = new ResourceDownloader() {
            public void download(Artifact artifact, Resource resource, File dest)
                    throws IOException {
                String content = "<ivy-module version=\"2.0\"><info organisation=\"org\" module=\"module\" status=\"integration\" revision=\"1.1\" branch=\"trunk\"/></ivy-module>";
                dest.getParentFile().mkdirs();
                FileOutputStream out = new FileOutputStream(dest);
                PrintWriter pw = new PrintWriter(out);
                pw.write(content);
                pw.flush();
                out.close();
            }
        };
        ModuleDescriptorWriter writer = new ModuleDescriptorWriter() {
            public void write(ResolvedResource originalMdResource, ModuleDescriptor md, File src,
                              File dest) throws IOException {
                XmlModuleDescriptorWriter.write(md, dest);
            }
        };

        // latest.integration will resolve to 1.1 in resolver1
        ModuleRevisionId mrid11 = new ModuleRevisionId(mi, "trunk", "1.1");
        DefaultArtifact artifact11 = new DefaultArtifact(mrid11, new Date(), "module-1.1.ivy", "ivy", "ivy", true);
        DependencyDescriptor dd11 = new DefaultDependencyDescriptor(mrid11, false);
        BasicResource resource11 = new BasicResource("/module-1-1.ivy", true, 1, 0, true);
        ResolvedResource mdRef11 = new ResolvedResource(resource11, "1.1");

        // tell the cache about 1.1
        ResolvedModuleRevision rmr11 = cacheManager.cacheModuleDescriptor(resolver, mdRef11, dd11, artifact11, downloader, options);
        cacheManager.originalToCachedModuleDescriptor(resolver, mdRef11, artifact11, rmr11, writer);
        // and use the new overload that passes in resolver name
        cacheManager.saveResolvedRevision("resolver1", mridLatest, "1.1");

        ResolvedModuleRevision rmrFromCache = cacheManager.findModuleInCache(ddLatest, mridLatest, options, "resolver1");
        assertEquals(rmr11, rmrFromCache);
    }

    private static DefaultArtifact createArtifact(String org, String module, String rev,
                                                  String name, String type, String ext) {
        ModuleId mid = new ModuleId(org, module);
        ModuleRevisionId mrid = new ModuleRevisionId(mid, rev);
        return new DefaultArtifact(mrid, new Date(), name, type, ext);
    }

}
