/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.purl.sword.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import jakarta.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.Logger;
import org.purl.sword.atom.Author;
import org.purl.sword.atom.Content;
import org.purl.sword.atom.Contributor;
import org.purl.sword.atom.Generator;
import org.purl.sword.atom.InvalidMediaTypeException;
import org.purl.sword.atom.Link;
import org.purl.sword.atom.Summary;
import org.purl.sword.atom.Title;
import org.purl.sword.base.AtomDocumentRequest;
import org.purl.sword.base.AtomDocumentResponse;
import org.purl.sword.base.Collection;
import org.purl.sword.base.Deposit;
import org.purl.sword.base.DepositResponse;
import org.purl.sword.base.ErrorCodes;
import org.purl.sword.base.SWORDAuthenticationException;
import org.purl.sword.base.SWORDEntry;
import org.purl.sword.base.SWORDErrorException;
import org.purl.sword.base.SWORDException;
import org.purl.sword.base.Service;
import org.purl.sword.base.ServiceDocument;
import org.purl.sword.base.ServiceDocumentRequest;
import org.purl.sword.base.Workspace;

/**
 * A 'dummy server' which acts as dumb repository which implements the
 * SWORD ServerInterface. It accepts any type of deposit, and tries to
 * return appropriate responses.
 *
 * It supports authentication: if the username and password match
 * (case sensitive) it authenticates the user, if not, the authentication
 * fails.
 *
 * @author Stuart Lewis
 */
public class DummyServer implements SWORDServer {

    /**
     * A counter to count submissions, so the response to a deposit can increment
     */
    private static int counter = 0;

    /**
     * Logger
     */
    private static Logger log = org.apache.logging.log4j.LogManager.getLogger(ServiceDocumentServlet.class);

    /**
     * Provides a dumb but plausible service document - it contains
     * an anonymous workspace and collection, and one personalised
     * for the onBehalfOf user.
     *
     * @param sdr The request
     * @throws SWORDAuthenticationException If the credentials are bad
     * @throws SWORDErrorException          If something goes wrong, such as
     */
    public ServiceDocument doServiceDocument(ServiceDocumentRequest sdr)
        throws SWORDAuthenticationException, SWORDErrorException,
        SWORDException {
        // Authenticate the user
        String username = sdr.getUsername();
        String password = sdr.getPassword();
        if ((username != null) && (password != null) &&
            (((username.equals("")) && (password.equals(""))) ||
                (!username.equalsIgnoreCase(password)))) {
            // User not authenticated
            throw new SWORDAuthenticationException("Bad credentials");
        }

        // Allow users to force the throwing of a SWORD error exception by setting
        // the OBO user to 'error'
        if ((sdr.getOnBehalfOf() != null) && (sdr.getOnBehalfOf().equals("error"))) {
            // Throw the error exception
            throw new SWORDErrorException(ErrorCodes.MEDIATION_NOT_ALLOWED, "Mediated deposits not allowed");
        }

        // Create and return a dummy ServiceDocument
        ServiceDocument document = new ServiceDocument();
        Service service = new Service("1.3", true, true);
        document.setService(service);
        log.debug("sdr.getLocation() is: " + sdr.getLocation());
        String location = sdr.getLocation().substring(0, sdr.getLocation().length() - 16);
        log.debug("location is: " + location);

        if (sdr.getLocation().contains("?nested=")) {
            Workspace workspace = new Workspace();
            workspace.setTitle("Nested service document workspace");
            Collection collection = new Collection();
            collection
                .setTitle("Nested collection: " + sdr.getLocation().substring(sdr.getLocation().indexOf('?') + 1));
            collection.setLocation(location + "/deposit/nested");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit");
            collection.addAccepts("application/zip");
            collection.addAccepts("application/xml");
            collection.setAbstract("A nested collection that users can deposit into");
            collection.setTreatment("This is a dummy server");
            collection.setCollectionPolicy(
                "No guarantee of service, or that deposits will be retained for any length of time.");
            workspace.addCollection(collection);
            service.addWorkspace(workspace);
        } else {
            Workspace workspace = new Workspace();
            workspace.setTitle("Anonymous submitters workspace");
            Collection collection = new Collection();
            collection.setTitle("Anonymous submitters collection");
            collection.setLocation(location + "/deposit/anon");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit");
            collection.addAccepts("application/zip");
            collection.addAccepts("application/xml");
            collection.setAbstract("A collection that anonymous users can deposit into");
            collection.setTreatment("This is a dummy server");
            collection.setCollectionPolicy(
                "No guarantee of service, or that deposits will be retained for any length of time.");
            collection.setService(location + "/client/servicedocument?nested=anon");
            workspace.addCollection(collection);
            collection = new Collection();
            collection.setTitle("Anonymous submitters other collection");
            collection.setLocation(location + "/deposit/anonymous");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit");
            collection.addAccepts("application/zip");
            collection.addAccepts("application/xml");
            collection.setAbstract("Another collection that anonymous users can deposit into");
            collection.setTreatment("This is a dummy server");
            collection.setCollectionPolicy(
                "No guarantee of service, or that deposits will be retained for any length of time.");
            workspace.addCollection(collection);
            service.addWorkspace(workspace);

            if (sdr.getUsername() != null) {
                workspace = new Workspace();
                workspace.setTitle("Authenticated workspace for " + username);
                collection = new Collection();
                collection.setTitle("Authenticated collection for " + username);
                collection.setLocation(location + "/deposit/" + username);
                collection.addAccepts("application/zip");
                collection.addAccepts("application/xml");
                collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
                collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit", 0.8f);
                collection.setAbstract("A collection that " + username + " can deposit into");
                collection.setTreatment("This is a dummy server");
                collection.setCollectionPolicy(
                    "No guarantee of service, or that deposits will be retained for any length of time.");
                collection.setService(location + "/client/servicedocument?nested=authenticated");
                workspace.addCollection(collection);
                collection = new Collection();
                collection.setTitle("Second authenticated collection for " + username);
                collection.setLocation(location + "/deposit/" + username + "-2");
                collection.addAccepts("application/zip");
                collection.addAccepts("application/xml");
                collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit", 0.123f);
                collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
                collection.setAbstract("A collection that " + username + " can deposit into");
                collection.setTreatment("This is a dummy server");
                collection.setCollectionPolicy(
                    "No guarantee of service, or that deposits will be retained for any length of time.");
                workspace.addCollection(collection);
            }
            service.addWorkspace(workspace);
        }

        String onBehalfOf = sdr.getOnBehalfOf();
        if ((onBehalfOf != null) && (!onBehalfOf.equals(""))) {
            Workspace workspace = new Workspace();
            workspace.setTitle("Personal workspace for " + onBehalfOf);
            Collection collection = new Collection();
            collection.setTitle("Personal collection for " + onBehalfOf);
            collection.setLocation(location + "/deposit?user=" + onBehalfOf);
            collection.addAccepts("application/zip");
            collection.addAccepts("application/xml");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/METSDSpaceSIP");
            collection.addAcceptPackaging("http://purl.org/net/sword-types/bagit", 0.8f);
            collection.setAbstract("An abstract goes in here");
            collection.setCollectionPolicy("A collection policy");
            collection.setMediation(true);
            collection.setTreatment("treatment in here too");
            workspace.addCollection(collection);
            service.addWorkspace(workspace);
        }

        return document;
    }

    public DepositResponse doDeposit(Deposit deposit)
        throws SWORDAuthenticationException, SWORDErrorException, SWORDException {
        // Authenticate the user
        String username = deposit.getUsername();
        String password = deposit.getPassword();
        if ((username != null) && (password != null) &&
            (((username.equals("")) && (password.equals(""))) ||
                (!username.equalsIgnoreCase(password)))) {
            // User not authenticated
            throw new SWORDAuthenticationException("Bad credentials");
        }

        // Check this is a collection that takes obo deposits, else thrown an error
        if (((deposit.getOnBehalfOf() != null) && (!deposit.getOnBehalfOf().equals(""))) &&
            (!deposit.getLocation().contains("deposit?user="))) {
            throw new SWORDErrorException(ErrorCodes.MEDIATION_NOT_ALLOWED,
                                          "Mediated deposit not allowed to this collection");
        }

        // Get the filenames
        StringBuffer filenames = new StringBuffer("Deposit file contained: ");
        if (deposit.getFilename() != null) {
            filenames.append("(filename = " + deposit.getFilename() + ") ");
        }
        if (deposit.getSlug() != null) {
            filenames.append("(slug = " + deposit.getSlug() + ") ");
        }

        ZipInputStream zip = null;
        try {
            File depositFile = deposit.getFile();
            zip = new ZipInputStream(new FileInputStream(depositFile));
            ZipEntry ze;
            while ((ze = zip.getNextEntry()) != null) {
                filenames.append(" ").append(ze.toString());
            }
        } catch (IOException ioe) {
            throw new SWORDException("Failed to open deposited zip file", ioe, ErrorCodes.ERROR_CONTENT);
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (IOException e) {
                    log.error("Unable to close zip stream", e);
                }
            }
        }

        // Handle the deposit
        if (!deposit.isNoOp()) {
            counter++;
        }
        DepositResponse dr = new DepositResponse(Deposit.CREATED);
        SWORDEntry se = new SWORDEntry();

        Title t = new Title();
        t.setContent("DummyServer Deposit: #" + counter);
        se.setTitle(t);

        se.addCategory("Category");

        if (deposit.getSlug() != null) {
            se.setId(deposit.getSlug() + " - ID: " + counter);
        } else {
            se.setId("ID: " + counter);
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        TimeZone utc = TimeZone.getTimeZone("UTC");
        sdf.setTimeZone(utc);
        String milliFormat = sdf.format(new Date());
        se.setUpdated(milliFormat);

        Summary s = new Summary();
        s.setContent(filenames.toString());
        se.setSummary(s);
        Author a = new Author();
        if (username != null) {
            a.setName(username);
        } else {
            a.setName("unknown");
        }
        se.addAuthors(a);

        Link em = new Link();
        em.setRel("edit-media");
        em.setHref("http://www.myrepository.ac.uk/sdl/workflow/my deposit");
        se.addLink(em);

        Link e = new Link();
        e.setRel("edit");
        e.setHref("http://www.myrepository.ac.uk/sdl/workflow/my deposit.atom");
        se.addLink(e);

        if (deposit.getOnBehalfOf() != null) {
            Contributor c = new Contributor();
            c.setName(deposit.getOnBehalfOf());
            c.setEmail(deposit.getOnBehalfOf() + "@myrepository.ac.uk");
            se.addContributor(c);
        }

        Generator generator = new Generator();
        generator.setContent("Stuart's Dummy SWORD Server");
        generator.setUri("http://dummy-sword-server.example.com/");
        generator.setVersion("1.3");
        se.setGenerator(generator);

        Content content = new Content();
        try {
            content.setType("application/zip");
        } catch (InvalidMediaTypeException ex) {
            ex.printStackTrace();
        }
        content.setSource("http://www.myrepository.ac.uk/sdl/uploads/upload-" + counter + ".zip");
        se.setContent(content);

        se.setTreatment("Short back and sides");

        if (deposit.isVerbose()) {
            se.setVerboseDescription("I've done a lot of hard work to get this far!");
        }

        se.setNoOp(deposit.isNoOp());

        dr.setEntry(se);

        dr.setLocation("http://www.myrepository.ac.uk/atom/" + counter);

        return dr;
    }

    public AtomDocumentResponse doAtomDocument(AtomDocumentRequest adr)
        throws SWORDAuthenticationException, SWORDErrorException, SWORDException {
        // Authenticate the user
        String username = adr.getUsername();
        String password = adr.getPassword();
        if ((username != null) && (password != null) &&
            (((username.equals("")) && (password.equals(""))) ||
                (!username.equalsIgnoreCase(password)))) {
            // User not authenticated
            throw new SWORDAuthenticationException("Bad credentials");
        }

        return new AtomDocumentResponse(HttpServletResponse.SC_OK);
    }

}
