package org.qortal.repository.hsqldb;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.qortal.data.asset.OrderData;
import org.qortal.data.chat.ActiveChats;
import org.qortal.data.chat.ActiveChats.DirectChat;
import org.qortal.data.chat.ActiveChats.GroupChat;
import org.qortal.data.chat.ChatMessage;
import org.qortal.repository.ChatRepository;
import org.qortal.repository.DataException;
import org.qortal.transaction.Transaction.TransactionType;

public class HSQLDBChatRepository implements ChatRepository {

	protected HSQLDBRepository repository;

	public HSQLDBChatRepository(HSQLDBRepository repository) {
		this.repository = repository;
	}

	@Override
	public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes,
			byte[] chatReferenceBytes, Boolean hasChatReference, List<String> involving,
			Integer limit, Integer offset, Boolean reverse) throws DataException {
		// Check args meet expectations
		if ((txGroupId != null && involving != null && !involving.isEmpty())
				|| (txGroupId == null && (involving == null || involving.size() != 2)))
			throw new DataException("Invalid criteria for fetching chat messages from repository");

		StringBuilder sql = new StringBuilder(1024);

		sql.append("SELECT created_when, tx_group_id, Transactions.reference, creator, sender, recipient, "
				// TODO: + "SenderNames.name, RecipientNames.name, "
				+ "chat_reference, data, is_text, is_encrypted, signature "
				+ "FROM ChatMessages ");
				// TODO: + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
				// TODO: + "LEFT OUTER JOIN Names AS RecipientNames ON RecipientNames.owner = recipient ");

		// WHERE clauses

		List<String> whereClauses = new ArrayList<>();
		List<Object> bindParams = new ArrayList<>();

		// Timestamp range
		if (before != null) {
			whereClauses.add("created_when < ?");
			bindParams.add(before);
		}

		if (after != null) {
			whereClauses.add("created_when > ?");
			bindParams.add(after);
		}

		if (referenceBytes != null) {
			whereClauses.add("reference = ?");
			bindParams.add(referenceBytes);
		}

		if (chatReferenceBytes != null) {
			whereClauses.add("chat_reference = ?");
			bindParams.add(chatReferenceBytes);
		}

		if (hasChatReference != null && hasChatReference == true) {
			whereClauses.add("chat_reference IS NOT NULL");
		}
		else if (hasChatReference != null && hasChatReference == false) {
			whereClauses.add("chat_reference IS NULL");
		}

		if (txGroupId != null) {
			whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally
			whereClauses.add("recipient IS NULL");
		} else {
			whereClauses.add("((sender = ? AND recipient = ?) OR (recipient = ? AND sender = ?))");
			bindParams.addAll(involving);
			bindParams.addAll(involving);
		}

		if (!whereClauses.isEmpty()) {
			sql.append(" WHERE ");

			final int whereClausesSize = whereClauses.size();
			for (int wci = 0; wci < whereClausesSize; ++wci) {
				if (wci != 0)
					sql.append(" AND ");

				sql.append(whereClauses.get(wci));
			}
		}

		sql.append(" ORDER BY created_when");
		sql.append((reverse == null || !reverse) ? " ASC" : " DESC");

		HSQLDBRepository.limitOffsetSql(sql, limit, offset);

		List<ChatMessage> chatMessages = new ArrayList<>();

		try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
			if (resultSet == null)
				return chatMessages;

			do {
				long timestamp = resultSet.getLong(1);
				int groupId = resultSet.getInt(2);
				byte[] reference = resultSet.getBytes(3);
				byte[] senderPublicKey = resultSet.getBytes(4);
				String sender = resultSet.getString(5);
				String senderName = resultSet.getString(6);
				String recipient = resultSet.getString(7);
				String recipientName = resultSet.getString(8);
				byte[] chatReference = resultSet.getBytes(9);
				byte[] data = resultSet.getBytes(10);
				boolean isText = resultSet.getBoolean(11);
				boolean isEncrypted = resultSet.getBoolean(12);
				byte[] signature = resultSet.getBytes(13);

				ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
						senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature);

				chatMessages.add(chatMessage);
			} while (resultSet.next());

			return chatMessages;
		} catch (SQLException e) {
			throw new DataException("Unable to fetch matching chat messages from repository", e);
		}
	}

	@Override
	public ActiveChats getActiveChats(String address) throws DataException {
		List<GroupChat> groupChats = new ArrayList<>(); // TODO: getActiveGroupChats(address);
		List<DirectChat> directChats = getActiveDirectChats(address);

		return new ActiveChats(groupChats, directChats);
	}

//	private List<GroupChat> getActiveGroupChats(String address) throws DataException {
//		// TODO: needs completely rethinking
//		// Find groups where address is a member and potential latest message details
//		String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name "
//				+ "FROM GroupMembers "
//				+ "JOIN Groups USING (group_id) "
//				+ "LEFT OUTER JOIN LATERAL("
//					+ "SELECT created_when AS latest_timestamp, sender, name AS sender_name "
//					+ "FROM ChatTransactions "
//					+ "JOIN Transactions USING (signature) "
//					+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
//					// NOTE: We need to qualify "Groups.group_id" here to avoid "General error" bug in HSQLDB v2.5.0
//					+ "WHERE tx_group_id = Groups.group_id AND type = " + TransactionType.CHAT.value + " "
//					+ "ORDER BY created_when DESC "
//					+ "LIMIT 1"
//				+ ") AS LatestMessages ON TRUE "
//				+ "WHERE address = ?";
//
//		List<GroupChat> groupChats = new ArrayList<>();
//		try (ResultSet resultSet = this.repository.checkedExecute(groupsSql, address)) {
//			if (resultSet != null) {
//				do {
//					int groupId = resultSet.getInt(1);
//					String groupName = resultSet.getString(2);
//
//					Long timestamp = resultSet.getLong(3);
//					if (timestamp == 0 && resultSet.wasNull())
//						timestamp = null;
//
//					String sender = resultSet.getString(4);
//					String senderName = resultSet.getString(5);
//
//					GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName);
//					groupChats.add(groupChat);
//				} while (resultSet.next());
//			}
//		} catch (SQLException e) {
//			throw new DataException("Unable to fetch active group chats from repository", e);
//		}
//
//		// We need different SQL to handle group-less chat
//		String grouplessSql = "SELECT created_when, sender, SenderNames.name "
//				+ "FROM ChatTransactions "
//				+ "JOIN Transactions USING (signature) "
//				+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
//				+ "WHERE tx_group_id = 0 "
//				+ "AND recipient IS NULL "
//				+ "ORDER BY created_when DESC "
//				+ "LIMIT 1";
//
//		try (ResultSet resultSet = this.repository.checkedExecute(grouplessSql)) {
//			Long timestamp = null;
//			String sender = null;
//			String senderName = null;
//
//			if (resultSet != null) {
//				// We found a recipient-less, group-less CHAT message, so report its details
//				timestamp = resultSet.getLong(1);
//				sender = resultSet.getString(2);
//				senderName = resultSet.getString(3);
//			}
//
//			GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName);
//			groupChats.add(groupChat);
//		} catch (SQLException e) {
//			throw new DataException("Unable to fetch active group chats from repository", e);
//		}
//
//		return groupChats;
//	}

	private List<DirectChat> getActiveDirectChats(String address) throws DataException {
		// Find chat messages involving address
		String directSql = "SELECT other_address, name, latest_timestamp, sender, sender_name "
				+ "FROM ("
					+ "SELECT recipient FROM ChatMessages "
					+ "WHERE sender = ? AND recipient IS NOT NULL "
					+ "UNION "
					+ "SELECT sender FROM ChatMessages "
					+ "WHERE recipient = ?"
				+ ") AS OtherParties (other_address) "
				+ "CROSS JOIN LATERAL("
					+ "SELECT created_when AS latest_timestamp, sender, name AS sender_name "
					+ "FROM ChatMessages "
					// TODO: + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
					+ "WHERE (sender = other_address AND recipient = ?) "
					+ "OR (sender = ? AND recipient = other_address) "
					+ "ORDER BY created_when DESC "
					+ "LIMIT 1"
				+ ") AS LatestMessages ";
				// TODO: + "LEFT OUTER JOIN Names ON owner = other_address";

		Object[] bindParams = new Object[] { address, address, address, address };

		List<DirectChat> directChats = new ArrayList<>();
		try (ResultSet resultSet = this.repository.checkedExecute(directSql, bindParams)) {
			if (resultSet == null)
				return directChats;

			do {
				String otherAddress = resultSet.getString(1);
				String name = resultSet.getString(2);
				long timestamp = resultSet.getLong(3);
				String sender = resultSet.getString(4);
				String senderName = "TODO: sender name "; //resultSet.getString(5); // TODO: fetch separately?

				DirectChat directChat = new DirectChat(otherAddress, name, timestamp, sender, senderName);
				directChats.add(directChat);
			} while (resultSet.next());
		} catch (SQLException e) {
			throw new DataException("Unable to fetch active direct chats from repository", e);
		}

		return directChats;
	}


	public ChatMessage getChatMessageBySignature(byte[] signature) throws DataException {
		StringBuilder sql = new StringBuilder(1024);

		sql.append("SELECT created_when, tx_group_id, reference, creator, sender, recipient, "
				// TODO: + "SenderNames.name, RecipientNames.name, "
				+ "data, is_text, is_encrypted, signature "
				+ "FROM ChatMessages WHERE signature = ?");
		// TODO: + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
		// TODO: + "LEFT OUTER JOIN Names AS RecipientNames ON RecipientNames.owner = recipient ");

		try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), signature)) {
			if (resultSet == null)
				return null;

			long timestamp = resultSet.getLong(1);
			int groupId = resultSet.getInt(2);
			byte[] reference = resultSet.getBytes(3);
			byte[] senderPublicKey = resultSet.getBytes(4);
			String sender = resultSet.getString(5);
			String senderName = resultSet.getString(6); // TOOD
			String recipient = resultSet.getString(7);
			String recipientName = resultSet.getString(8); // TODO
			byte[] data = resultSet.getBytes(9);
			boolean isText = resultSet.getBoolean(10);
			boolean isEncrypted = resultSet.getBoolean(11);
			byte[] signatureResult = resultSet.getBytes(12);

			ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
					senderName, recipient, recipientName, data, isText, isEncrypted, signatureResult);

			return chatMessage;
		} catch (SQLException e) {
			throw new DataException("Unable to fetch chat message from repository", e);
		}
	}

	@Override
	public void save(ChatMessage chatMessage) throws DataException {
		HSQLDBSaver saveHelper = new HSQLDBSaver("ChatMessages");

		saveHelper.bind("timestamp", chatMessage.getTimestamp()).bind("tx_group_id", chatMessage.getTxGroupId())
				.bind("reference", chatMessage.getReference()).bind("sender_public_key", chatMessage.getSenderPublicKey())
				.bind("sender", chatMessage.getSignature()).bind("recipient", chatMessage.getRecipient())
				.bind("data", chatMessage.getData()).bind("is_text", chatMessage.isText())
				.bind("is_encrypted", chatMessage.isEncrypted()).bind("signature", chatMessage.getSignature());

		try {
			saveHelper.execute(this.repository);
		} catch (SQLException e) {
			throw new DataException("Unable to save chat message into repository", e);
		}
	}

}
