package de.metas.ui.web.attachments;

import com.google.common.collect.ImmutableList;
import de.metas.attachments.AttachmentEntry;
import de.metas.attachments.AttachmentEntryService;
import de.metas.attachments.listener.TableAttachmentListenerService;
import de.metas.security.IUserRolePermissions;
import de.metas.ui.web.attachments.json.JSONAttachURLRequest;
import de.metas.ui.web.attachments.json.JSONAttachment;
import de.metas.ui.web.exceptions.EntityNotFoundException;
import de.metas.ui.web.session.UserSession;
import de.metas.ui.web.window.controller.WindowRestController;
import de.metas.ui.web.window.datatypes.DocumentId;
import de.metas.ui.web.window.datatypes.DocumentPath;
import de.metas.ui.web.window.datatypes.WindowId;
import de.metas.ui.web.window.descriptor.factory.DocumentDescriptorFactory;
import de.metas.ui.web.window.events.DocumentWebsocketPublisher;
import lombok.NonNull;
import org.adempiere.exceptions.AdempiereException;
import org.adempiere.util.lang.impl.TableRecordReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2017 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

@RestController
@RequestMapping(value = DocumentAttachmentsRestController.ENDPOINT)
public class DocumentAttachmentsRestController
{
	public static final String ENDPOINT = WindowRestController.ENDPOINT + "/{windowId}/{documentId}/attachments";

	private UserSession userSession;

	private DocumentDescriptorFactory documentDescriptorFactory;

	private DocumentWebsocketPublisher websocketPublisher;

	private AttachmentEntryService attachmentEntryService;

	private TableAttachmentListenerService tableAttachmentListenerService;

	public DocumentAttachmentsRestController(final UserSession userSession,
			final DocumentDescriptorFactory documentDescriptorFactory,
			final DocumentWebsocketPublisher websocketPublisher,
			final AttachmentEntryService attachmentEntryService,
			final TableAttachmentListenerService tableAttachmentListenerService)
	{
		this.userSession = userSession;
		this.documentDescriptorFactory = documentDescriptorFactory;
		this.websocketPublisher = websocketPublisher;
		this.attachmentEntryService = attachmentEntryService;
		this.tableAttachmentListenerService = tableAttachmentListenerService;
	}

	private DocumentAttachments getDocumentAttachments(final String windowIdStr, final String documentId)
	{
		final DocumentPath documentPath = DocumentPath.rootDocumentPath(WindowId.fromJson(windowIdStr), documentId);
		return getDocumentAttachments(documentPath);
	}

	private DocumentAttachments getDocumentAttachments(final DocumentPath documentPath)
	{
		if (documentPath.isComposedKey())
		{
			throw new AdempiereException("Document does not support attachments")
					.setParameter("technicalReason", "documents with composed keys are not handled");
		}

		final TableRecordReference recordRef = documentDescriptorFactory.getTableRecordReference(documentPath);

		return DocumentAttachments.builder()
				.documentPath(documentPath)
				.recordRef(recordRef)
				.entityDescriptor(documentDescriptorFactory.getDocumentEntityDescriptor(documentPath))
				.websocketPublisher(websocketPublisher)
				.tableAttachmentListenerService(tableAttachmentListenerService)
				.attachmentEntryService(attachmentEntryService)
				.build();
	}

	/**
	 * Attaches a file to given root document.
	 */
	@PostMapping
	public void attachFile(
			@PathVariable("windowId") final String windowIdStr //
			, @PathVariable("documentId") final String documentId //
			, @RequestParam("file") final MultipartFile file //
	) throws IOException
	{
		userSession.assertLoggedIn();

		getDocumentAttachments(windowIdStr, documentId)
				.addEntry(file);
	}

	@PostMapping("/addUrl")
	public void attachURL(
			@PathVariable("windowId") final String windowIdStr,
			@PathVariable("documentId") final String documentId,
			@RequestBody final JSONAttachURLRequest request)
	{
		userSession.assertLoggedIn();

		getDocumentAttachments(windowIdStr, documentId)
				.addURLEntry(request.getName(), request.getUri());
	}

	@GetMapping
	public List<JSONAttachment> getAttachments(
			@PathVariable("windowId") final String windowIdStr //
			, @PathVariable("documentId") final String documentId //
	)
	{
		userSession.assertLoggedIn();

		final DocumentPath documentPath = DocumentPath.rootDocumentPath(WindowId.fromJson(windowIdStr), documentId);
		if (documentPath.isComposedKey())
		{
			// document with composed keys does not support attachments
			return ImmutableList.of();
		}

		final boolean allowDelete = isAllowDeletingAttachments();

		final List<JSONAttachment> attachments = getDocumentAttachments(documentPath)
				.toJson();
		attachments.forEach(attachment -> attachment.setAllowDelete(allowDelete));

		return attachments;
	}

	private boolean isAllowDeletingAttachments()
	{
		return userSession
				.getUserRolePermissions()
				.hasPermission(IUserRolePermissions.PERMISSION_IsAttachmentDeletionAllowed);
	}

	@GetMapping("/{id}")
	public ResponseEntity<byte[]> getAttachmentById(
			@PathVariable("windowId") final String windowIdStr //
			, @PathVariable("documentId") final String documentId //
			, @PathVariable("id") final String entryIdStr)
	{
		userSession.assertLoggedIn();

		final DocumentId entryId = DocumentId.of(entryIdStr);
		final IDocumentAttachmentEntry entry = getDocumentAttachments(windowIdStr, documentId)
				.getEntry(entryId);

		final AttachmentEntry.Type type = entry.getType();
		if (type == AttachmentEntry.Type.Data)
		{
			return extractResponseEntryFromData(entry);
		}
		else if (type == AttachmentEntry.Type.URL)
		{
			return extractResponseEntryFromURL(entry);
		}
		else
		{
			throw new AdempiereException("Invalid attachment entry")
					.setParameter("reason", "invalid type")
					.setParameter("type", type)
					.setParameter("entry", entry);
		}
	}

	private static ResponseEntity<byte[]> extractResponseEntryFromData(@NonNull final IDocumentAttachmentEntry entry)
	{
		final String entryFilename = entry.getFilename();
		final byte[] entryData = entry.getData();
		if (entryData == null || entryData.length == 0)
		{
			throw new EntityNotFoundException("No attachment found")
					.setParameter("entry", entry)
					.setParameter("reason", "data is null or empty");
		}

		final String entryContentType = entry.getContentType();

		final HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.parseMediaType(entryContentType));
		headers.set(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + entryFilename + "\"");
		headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
		final ResponseEntity<byte[]> response = new ResponseEntity<>(entryData, headers, HttpStatus.OK);
		return response;
	}

	private static ResponseEntity<byte[]> extractResponseEntryFromURL(@NonNull final IDocumentAttachmentEntry entry)
	{
		final HttpHeaders headers = new HttpHeaders();
		headers.setLocation(entry.getUrl()); // forward to attachment entry's URL
		final ResponseEntity<byte[]> response = new ResponseEntity<>(new byte[] {}, headers, HttpStatus.FOUND);
		return response;
	}

	@DeleteMapping("/{id}")
	public void deleteAttachmentById(
			@PathVariable("windowId") final String windowIdStr //
			, @PathVariable("documentId") final String documentId //
			, @PathVariable("id") final String entryIdStr //
	)
	{
		userSession.assertLoggedIn();

		if (!isAllowDeletingAttachments())
		{
			throw new AdempiereException("Delete not allowed");
		}
		final DocumentId entryId = DocumentId.of(entryIdStr);
		getDocumentAttachments(windowIdStr, documentId)
				.deleteEntry(entryId);
	}

}