package whelk.export.servlet;

import org.codehaus.jackson.map.ObjectMapper;
import whelk.Document;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.sql.*;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import io.prometheus.client.Counter;

public class GetRecord
{
    private final static String IDENTIFIER_PARAM = "identifier";
    private final static String FORMAT_PARAM = "metadataPrefix";

    private static final Counter failedRequests = Counter.build()
            .name("oaipmh_failed_getrecord_requests_total").help("Total failed GetRecord requests.")
            .labelNames("error").register();

    /**
     * Verifies the integrity of a OAI-PMH request with the verb 'GetRecord' and sends a proper response.
     */
    public static void handleGetRecordRequest(HttpServletRequest request, HttpServletResponse response)
            throws IOException, XMLStreamException, SQLException
    {
        // Parse and verify the parameters allowed for this request
        String identifierUri = request.getParameter(IDENTIFIER_PARAM); // required
        String metadataPrefix = request.getParameter(FORMAT_PARAM); // required

        if (ResponseCommon.errorOnExtraParameters(request, response, IDENTIFIER_PARAM, FORMAT_PARAM))
            return;

        if (metadataPrefix == null)
        {
            failedRequests.labels(OaiPmh.OAIPMH_ERROR_BAD_ARGUMENT).inc();
            ResponseCommon.sendOaiPmhError(OaiPmh.OAIPMH_ERROR_BAD_ARGUMENT,
                    "metadataPrefix argument required.", request, response);
            return;
        }

        if (identifierUri == null)
        {
            failedRequests.labels(OaiPmh.OAIPMH_ERROR_BAD_ARGUMENT).inc();
            ResponseCommon.sendOaiPmhError(OaiPmh.OAIPMH_ERROR_BAD_ARGUMENT,
                    "identifier argument required.", request, response);
            return;
        }

        String id = Helpers.getShorthandDocumentId(identifierUri);

        try (Connection dbconn = OaiPmh.s_postgreSqlComponent.getConnection();
             PreparedStatement preparedStatement = prepareStatement(dbconn, id);
             ResultSet resultSet = preparedStatement.executeQuery())
        {
            if (!resultSet.next())
            {
                failedRequests.labels(OaiPmh.OAIPMH_ERROR_ID_DOES_NOT_EXIST).inc();
                ResponseCommon.sendOaiPmhError(OaiPmh.OAIPMH_ERROR_ID_DOES_NOT_EXIST, "", request, response);
                return;
            }

            ObjectMapper mapper = new ObjectMapper();
            String data = resultSet.getString("data");
            ZonedDateTime modified = ZonedDateTime.ofInstant(resultSet.getTimestamp("modified").toInstant(), ZoneOffset.UTC);
            HashMap datamap = mapper.readValue(data, HashMap.class);
            Document document = new Document(datamap);

            // Expanded format requested, we need to build trees.
            if (metadataPrefix.endsWith(OaiPmh.FORMAT_EXPANDED_POSTFIX))
            {
                List<String> nodeDatas = new LinkedList<String>();
                HashSet<String> visitedIDs = new HashSet<String>();
                ListRecordTrees.ModificationTimes modificationTimes = new ListRecordTrees.ModificationTimes();
                modificationTimes.earliestModification = modified;
                modificationTimes.latestModification = modified;

                ListRecordTrees.addNodeAndSubnodesToTree(id, visitedIDs, nodeDatas, modificationTimes);

                // Value of modificationTimes.latestModification will have changed during tree building.
                modified = modificationTimes.latestModification;

                document = ListRecordTrees.mergeDocument(id, nodeDatas);
            }

            // Build the xml response feed
            XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
            xmlOutputFactory.setProperty("escapeCharacters", false); // Inline xml must be left untouched.
            XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(response.getOutputStream());

            ResponseCommon.writeOaiPmhHeader(writer, request, true);
            writer.writeStartElement("GetRecord");

            ResponseCommon.emitRecord(resultSet, document, writer, metadataPrefix, false);

            writer.writeEndElement(); // GetRecord
            ResponseCommon.writeOaiPmhClose(writer, request);
        }
    }

    private static PreparedStatement prepareStatement(Connection dbconn, String id)
            throws SQLException
    {
        String tableName = OaiPmh.configuration.getProperty("sqlMaintable");

        // Construct the query
        String selectSQL = "SELECT data, collection, modified, deleted, data#>>'{@graph,1,heldBy,@id}' AS sigel FROM " +
                tableName + " WHERE id = ? AND collection <> 'definitions' ";
        PreparedStatement preparedStatement = dbconn.prepareStatement(selectSQL);
        preparedStatement.setString(1, id);

        return preparedStatement;
    }
}
@@ -2217,54 +2217,361 @@
 d = 
-Helpers.getShorthandDocumentId(identifierUri);
+null;%0A        try (Connection dbconn = OaiPmh.s_postgreSqlComponent.getConnection();%0A             PreparedStatement preparedStatement = prepareSameAsStatement(dbconn, identifierUri);%0A             ResultSet resultSet = preparedStatement.executeQuery())%0A        %7B%0A            if (resultSet.next())%0A                id = resultSet.getString(%22id%22);%0A        %7D
 %0A%0A  
@@ -2639,32 +2639,32 @@
 etConnection();%0A
-
              Pre
@@ -2697,32 +2697,48 @@
 tement = prepare
+MatchingDocument
 Statement(dbconn
@@ -5080,24 +5080,40 @@
 ment prepare
+MatchingDocument
 Statement(Co
@@ -5652,10 +5652,470 @@
 t;%0A    %7D
+%0A%0A    private static PreparedStatement prepareSameAsStatement(Connection dbconn, String id)%0A            throws SQLException%0A    %7B%0A        String tableName = OaiPmh.configuration.getProperty(%22sqlMaintable%22);%0A%0A        String sql = %22SELECT id FROM %22 + tableName + %22__identifiers WHERE iri = ?%22;%0A        PreparedStatement preparedStatement = dbconn.prepareStatement(sql);%0A        preparedStatement.setString(1, id);%0A        %0A        return preparedStatement;%0A    %7D
 %0A%7D

Add support in OAIPMH server for doing GetRecord on record and item sameAs.