package org.kar.karideo.api;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;

import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.kar.archidata.annotation.AsyncType;
import org.kar.archidata.annotation.TypeScriptProgress;
import org.kar.archidata.api.DataResource;
import org.kar.archidata.dataAccess.DBAccess;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.addOnSQL.AddOnDataJson;
import org.kar.archidata.exception.FailException;
import org.kar.archidata.exception.InputException;
import org.kar.archidata.model.Data;
import org.kar.archidata.tools.DataTools;
import org.kar.karideo.model.Media;
import org.kar.karideo.model.Season;
import org.kar.karideo.model.Series;
import org.kar.karideo.model.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/media")
@Produces(MediaType.APPLICATION_JSON)
public class MediaResource {
	static final Logger LOGGER = LoggerFactory.getLogger(MediaResource.class);

	@GET
	@RolesAllowed("USER")
	@Operation(description = "Get all Media", tags = "GLOBAL")
	public List<Media> gets() throws Exception {
		return DataAccess.gets(Media.class);
	}

	@GET
	@Path("{id}")
	@RolesAllowed("USER")
	@Operation(description = "Get a specific Media with his ID", tags = "GLOBAL")
	public Media get(@PathParam("id") final Long id) throws Exception {
		return DataAccess.get(Media.class, id);
	}

	@PATCH
	@Path("{id}")
	@RolesAllowed("ADMIN")
	@Consumes(MediaType.APPLICATION_JSON)
	@Operation(description = "Modify a specific Media", tags = "GLOBAL")
	public Media patch(@PathParam("id") final Long id, @AsyncType(Media.class) final String jsonRequest) throws Exception {
		LOGGER.info("update video {} ==> '{}'", id, jsonRequest);
		DataAccess.updateWithJson(Media.class, id, jsonRequest);
		return DataAccess.get(Media.class, id);
	}

	private String multipartCorrection(final String data) {
		if (data == null) {
			return null;
		}
		if (data.isEmpty()) {
			return null;
		}
		if (data.contentEquals("null")) {
			return null;
		}
		if (data.contentEquals("undefined")) {
			return null;
		}
		return data;
	}

	@POST
	@RolesAllowed("ADMIN")
	@Consumes({ MediaType.MULTIPART_FORM_DATA })
	@Operation(description = "Create a new Media", tags = "GLOBAL")
	@TypeScriptProgress
	public Media uploadMedia( //
			// @AsyncType(Long.class) @FormDataParam("universeId") String universeId, //
			// @AsyncType(Long.class) @FormDataParam("typeId") String typeId, //
			// @AsyncType(Long.class) @FormDataParam("seriesId") String seriesId, //
			// @AsyncType(Long.class) @FormDataParam("season") String season, // value of the season ==> local add if needed
			// @AsyncType(Long.class) @FormDataParam("episode") String episode, // value of the season ==> local add if needed
			@FormDataParam("universeId") String universeId, //
			@FormDataParam("typeId") String typeId, //
			@FormDataParam("seriesId") String seriesId, //
			@FormDataParam("season") String season, // value of the season ==> local add if needed
			@FormDataParam("episode") String episode, // value of the season ==> local add if needed
			@FormDataParam("title") String title, //
			@FormDataParam("file") final InputStream fileInputStream, //
			@FormDataParam("file") final FormDataContentDisposition fileMetaData //
	) throws FailException {
		try (DBAccess db = DBAccess.createInterface()) {
			// correct input string stream :
			final String fileName = multipartCorrection(fileMetaData.getFileName());
			universeId = multipartCorrection(universeId);
			final Long universeIdLong = universeId != null ? Long.parseLong(universeId) : null;
			typeId = multipartCorrection(typeId);
			final Long typeIdLong = typeId != null ? Long.parseLong(typeId) : null;
			seriesId = multipartCorrection(seriesId);
			final Long seriesIdLong = seriesId != null ? Long.parseLong(seriesId) : null;
			season = multipartCorrection(season);
			final Long seasonLong = season != null ? Long.parseLong(season) : null;
			episode = multipartCorrection(episode);
			title = multipartCorrection(title);

			// todo: check if all remotes Id exist ...

			// public NodeSmall uploadFile(final FormDataMultiPart form) {
			LOGGER.info("Upload media file: {}", fileMetaData);
			LOGGER.info("    - fileName: {}", fileName);
			LOGGER.info("    - universe: {}", universeIdLong);
			LOGGER.info("    - series: {}", seriesIdLong);
			LOGGER.info("    - season: {}", seasonLong);
			LOGGER.info("    - episode: {}", episode);
			LOGGER.info("    - title: {}", title);
			LOGGER.info("    - type: {}", typeId);
			LOGGER.info("    - fileInputStream: {}", fileInputStream);
			LOGGER.info("    - fileMetaData: {}", fileMetaData);
			System.out.flush();
			if (typeId == null) {
				throw new InputException("typeId", "TypiId is not specified");
			}

			final long tmpUID = DataResource.getTmpDataId();
			final String sha512 = DataResource.saveTemporaryFile(fileInputStream, tmpUID);
			Data data = DataTools.getWithSha512(db, sha512);
			if (data == null) {
				LOGGER.info("Need to add the data in the BDD ... ");
				System.out.flush();
				try {
					data = DataTools.createNewData(db, tmpUID, fileName, sha512);
				} catch (final IOException ex) {
					DataResource.removeTemporaryFile(tmpUID);
					ex.printStackTrace();
					throw new FailException("can not create input media (the data model has an internal error");
				}
			} else if (data != null && data.deleted != null && data.deleted) {
				LOGGER.info("Data already exist but deleted");
				System.out.flush();
				DataTools.undelete(db, data.oid);
				data.deleted = false;
			} else {
				LOGGER.info("Data already exist ... all good");
				System.out.flush();
			}
			// Fist step: retieve all the Id of each parents:...
			LOGGER.info("Find typeNode");
			// check if id of type exist:
			final Type typeNode = TypeResource.getId(typeIdLong);
			if (typeNode == null) {
				DataResource.removeTemporaryFile(tmpUID);
				throw new InputException("typeId", "TypeId does not exist ...");
			}
			// check if id of type exist:
			Series seriesNode = null;
			if (seriesIdLong != null) {
				seriesNode = SeriesResource.getId(seriesIdLong);
				if (seriesNode == null) {
					DataResource.removeTemporaryFile(tmpUID);
					throw new InputException("seriesId", "seriesId does not exist ...");
				}
				if (seriesNode.parentId != typeNode.id) {
					DataResource.removeTemporaryFile(tmpUID);
					throw new InputException("seriesId", "seriesId object have not the correct parent...");
				}
			}

			LOGGER.info("    ==> {}", seriesNode);
			LOGGER.info("Find seasonNode");
			// get uid of season:
			Season seasonNode = null;
			if (seriesNode == null && seasonLong != null) {
				DataResource.removeTemporaryFile(tmpUID);
				throw new InputException("season", "Season is set but no series is set !!");
			}
			if (season != null) {
				seasonNode = SeasonResource.getOrCreate(season, seriesNode.id);
			}

			LOGGER.info("    ==> {}", seasonNode);
			LOGGER.info("add media");

			try {
				final Media media = new Media();
				media.name = title;
				media.dataId = data.oid;
				media.typeId = typeNode.id;
				media.seriesId = null;
				if (seriesNode != null) {
					media.seriesId = seriesNode.id;
				}
				media.seasonId = null;
				if (seasonNode != null) {
					media.seasonId = seasonNode.id;
				}
				media.episode = null;
				if (episode != null && !episode.contentEquals("")) {
					media.episode = Integer.parseInt(episode);
				}
				final Media out = db.insert(media);
				LOGGER.info("Generate new media {}", out);
				return out;
			} catch (final SQLException ex) {
				ex.printStackTrace();
				LOGGER.error("Catch error: {}", ex.getMessage());
				throw new FailException("Catch SQLerror ==> check server logs");
			} finally {
				DataResource.removeTemporaryFile(tmpUID);
			}
		} catch (final Exception ex) {
			LOGGER.error("Catch an unexpected error ... {} ", ex.getMessage());
			ex.printStackTrace();
			throw new FailException("Catch Exception ==> check server logs");
		}
	}

	@POST
	@Path("{id}/cover")
	@RolesAllowed("ADMIN")
	@Consumes({ MediaType.MULTIPART_FORM_DATA })
	@AsyncType(Media.class)
	@Operation(description = "Upload a new season cover media", tags = "GLOBAL")
	@TypeScriptProgress
	public Media uploadCover( //
			@PathParam("id") final Long id, //
			@FormDataParam("file") final InputStream fileInputStream, //
			@FormDataParam("file") final FormDataContentDisposition fileMetaData//
	) throws Exception {
		try (DBAccess dbIo = DBAccess.createInterface()) {
			DataTools.uploadCover(dbIo, Media.class, id, fileInputStream, fileMetaData);
			return dbIo.get(Media.class, id);
		}
	}

	@DELETE
	@Path("{id}/cover/{coverId}")
	@RolesAllowed("ADMIN")
	@Operation(description = "Remove a specific cover of a media", tags = "GLOBAL")
	public Media removeCover( //
			@PathParam("id") final Long id, //
			@PathParam("coverId") final UUID coverId //
	) throws Exception {
		try (DBAccess dbIo = DBAccess.createInterface()) {
			AddOnDataJson.removeLink(dbIo, Media.class, "id", id, "covers", coverId);
			return dbIo.get(Media.class, id);
		}
	}

	@DELETE
	@Path("{id}")
	@RolesAllowed("ADMIN")
	@Operation(description = "Remove a specific Media", tags = "GLOBAL")
	public void remove(@PathParam("id") final Long id) throws Exception {
		DataAccess.delete(Media.class, id);
	}
}
