//
//  Messages.m
//  rainbow
//
//  Created by Doha Tubaileh on 9/30/19.
//  Copyright © 2019 Facebook. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "Messages.h"
#import <React/RCTLog.h>
#import "Rainbow/Rainbow.h"
#import "IEventEmmiter.h"
#import "Message+DictRepresentation.h"
#import "MessageEventsNames.h"
#import <Photos/Photos.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "RNRainbowRn.h"
#import "NSArray+ProcessingArray.h"
#import "Contact+DictRepresentation.h"
#define NB_MESSAGES_TO_RETRIEVE 10



@interface Messages ()
@property(nonatomic,strong) ConversationsManagerService *conversationManagerService;
@property (strong, nonatomic) Conversation *currentConversation;
@property (strong, nonatomic) MessagesBrowser *messagesBrowser;
@property (strong, nonatomic) NSMutableArray<Message *> *messages;
@property (strong, nonatomic) FileSharingService *fileSharingService;
@property (strong, nonatomic) File *attachmentFileToSend;
@property (nonatomic, strong) NSMutableArray<Contact *> *typingContacts;
@property (nonatomic, strong) IEventEmmiter *eventEmitter;
@property (nonatomic, weak) RCTBridge *bridge;

@end

@implementation Messages

RCT_EXPORT_MODULE();
- (instancetype)init {
    self = [super init];
    if (self) {
        //do any init here
        _conversationManagerService = [ServicesManager sharedInstance].conversationsManagerService;
        _fileSharingService = [ServicesManager sharedInstance].fileSharingService;
        _messages = [[NSMutableArray alloc] init];
        [self registerNotifications];
        _typingContacts = [NSMutableArray new];

    }
    return self;
}

- (void)dealloc {
    _conversationManagerService = nil;
    [self unRegisterNotifications];
}

-(void)didCallSuccess:(NSNotification *)notification {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self didCallSuccess:notification];
        });
        return;
    }
}

#pragma mark - ReactNative_functions

RCT_EXPORT_METHOD(getMessages:(NSString*)conversationId) {
    NSLog(@"[Messages] getMessages");
    [_messages removeAllObjects];
    _currentConversation = nil;
    [self searchConversationInMyList:conversationId];
    if (_currentConversation == nil) {
        //this conversation is new one, so start open conversation with him.
        [self startConversationWithNewConversation:conversationId];
    }
    _messagesBrowser = [_conversationManagerService messagesBrowserForConversation:self.currentConversation withPageSize:NB_MESSAGES_TO_RETRIEVE preloadMessages:YES];
    self.messagesBrowser.delegate = self;
    [_messagesBrowser resyncBrowsingCacheWithCompletionHandler:^(NSArray *addedCacheItems, NSArray *removedCacheItems, NSArray *updatedCacheItems, NSError *error) {
        NSLog(@"Resync done");
    }];
    if (_currentConversation.lastMessage == nil) {
        //this conversation has never chatted with, so send an empty array to stop the loading msg spinner
        [self sendEventWithName:MessagesUpdated eventBody:@[]];
    }
    //mark msg as read
    if (_currentConversation != nil) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self->_conversationManagerService markAllMessagesAsReadForConversation:self->_currentConversation];
            UIApplication.sharedApplication.applicationIconBadgeNumber = self->_conversationManagerService.totalNbOfUnreadMessagesInAllConversations;
        });
    }
}
 
#pragma mark - Sending Messages

RCT_EXPORT_METHOD(sendMessage:(NSString*)message urgency:(NSString*)urgency file:(NSArray<NSString *> *)filePaths) {
    NSMutableArray<File *> * attachmentFileToSend = [[NSMutableArray alloc] init];
    if (filePaths.count > 0) {
        for (NSString *uri in filePaths) {
            NSURL *url = [NSURL URLWithString:uri];
            NSData *fileData = [NSData dataWithContentsOfURL:url];
            NSString *fileName = url.lastPathComponent;
            [attachmentFileToSend addObject:[[ServicesManager sharedInstance].fileSharingService createTemporaryFileWithFileName:fileName andData:fileData andURL:url]];
        }
    }
    if (_currentConversation != nil) {
        NSLog(@"[%@] %@, with urgency: %@ and files counr: %lu",[self class], NSStringFromSelector(_cmd),urgency,(unsigned long)filePaths.count);
        [_conversationManagerService sendTextMessage:message files:attachmentFileToSend mentions:nil priority:[self encodeMessageType:urgency] repliedMessage:nil conversation:_currentConversation completionHandler:^(Message *message, NSError *error) {
            if (message.attachment) {
                NSMutableDictionary *attachedResult = [NSMutableDictionary new];
                NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.rainbowID == %@",message.attachment.rainbowID];
                File *attachedFile = [attachmentFileToSend filteredArrayUsingPredicate:predicate].firstObject;
                [attachedResult setValue:attachedFile.url.absoluteString forKey:@"uri"];
                if (error) {
                    NSLog(@"message completion: %@ with error: %@",message,error.description);
                    [attachedResult setValue:@NO forKey:@"isSuccess"];
                    [attachedResult setValue:error.localizedDescription forKey:@"errorMsg"];
                }
                else {
                    [attachedResult setValue:@YES forKey:@"isSuccess"];
                }
                [self sendEventWithName:FileAttachFinished eventBody:attachedResult];
            }
        }];
    }
}

RCT_EXPORT_METHOD(sendMessageWithAdditionalContentAndUrgency:(NSString*)conversationJid textMessage:(NSString*)textMessage alternativeContentMessage:(NSString*)alternativeContentMessage alternativeContentType:(NSString*)alternativeContentType urgency:(NSString*)urgency) {
    //TODO: adding sendMessageWithAdditionalContentAndUrgency when its supported
}

RCT_EXPORT_METHOD(forwardMessage:(NSString*)text selectedConverstion:(NSArray<NSString*>*)conversationsIds forwardedMsgId:(NSString*)forwardedId) {
    if (_currentConversation != nil) {
        Message *foundedMessge = nil;
        for (Message *object in self.messages) {
            if ([object.messageID isEqual: forwardedId]) {
                foundedMessge = object;
            }
        }
        //find conversation
        NSMutableArray<Conversation*> *conversationList = [[NSMutableArray alloc] init];
        for (int i = 0; i < conversationsIds.count; i++) {
            for (Conversation *object in _conversationManagerService.conversations) {
                if ([object.peer.jid isEqual: conversationsIds[i]]) {
                    [conversationList addObject:object];
                }
            }
        }
        [_conversationManagerService forwardMessage:foundedMessge withComment:text to:conversationList completionHandler:^(NSError * _Nullable error) {

        }];
    }
}


RCT_EXPORT_METHOD(replyMessage:(NSString*)text repliedMsgId:(NSString*)repliedId) {
    if (_currentConversation != nil) {
        Message *foundedMessge = nil;
        for (Message *object in self.messages) {
            if ([object.messageID isEqual: repliedId]) {
                foundedMessge = object;
            }
        }
        [_conversationManagerService sendTextMessage:text files:nil mentions:nil priority:MessagePriorityDefault repliedMessage:foundedMessge conversation:_currentConversation completionHandler:nil];
    }
}

RCT_EXPORT_METHOD(deleteMessage:(NSString*)msgID) {
    Message *foundedMessge = nil;
    for (Message *object in self.messages) {
        if ([object.messageID isEqual: msgID]) {
            foundedMessge = object;
        }
    }
    [_conversationManagerService modifyOrDeleteMessage:foundedMessge replacementText:nil to:_currentConversation completionHandler:^(Message *message, NSError *error) {
        NSLog(@"[Messages] deleteMessage: erro while deleting message");
    }];

    if (foundedMessge.attachment != nil) {
        [_fileSharingService deleteFile:foundedMessge.attachment];
    }
}

RCT_EXPORT_METHOD(loadMore) {
    NSLog(@"[Messages] loadMore");
    if (_messagesBrowser.hasMorePages) {
        [_messagesBrowser nextPageWithCompletionHandler:nil];
    }
}


RCT_EXPORT_METHOD(messagesReadInActiveConversation: (NSString*)conversationJid ) {
    [self->_conversationManagerService markAllMessagesAsReadForConversation:self->_currentConversation];
}

#pragma mark - Browsing delegate

-(void) itemsBrowser:(CKItemsBrowser*)browser didAddCacheItems:(NSArray*)newItems atIndexes:(NSIndexSet*)indexes {
    // Ensure you're on the main thread when needed
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self itemsBrowser:browser didAddCacheItems:newItems atIndexes:indexes];
        });
        return;
    }

    NSLog(@"%@ %@", NSStringFromSelector(_cmd), newItems);

    // Perform thread-safe addition of messages to the array
    NSMutableArray<Message *> *addedMessages = [NSMutableArray array]; // Collect added messages
    @synchronized (self.messages) {
        __block NSUInteger newItemIndex = 0;
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            Message *message = [newItems objectAtIndex:newItemIndex];
            [self.messages insertObject:message atIndex:idx];
            [addedMessages addObject:message]; // Collect the new message
            newItemIndex++;
        }];
    }

    // Dispatch UI-related updates to the main thread if needed
    dispatch_async(dispatch_get_main_queue(), ^{
        UIApplicationState state = UIApplication.sharedApplication.applicationState;
        if (state == UIApplicationStateBackground || state == UIApplicationStateInactive) {
            UIApplication.sharedApplication.applicationIconBadgeNumber = self->_conversationManagerService.totalNbOfUnreadMessagesInAllConversations;
        }
    });

    // Send messages in batch after the collection is complete
    if (addedMessages.count > 0) {
        NSArray *eventBody = [self processMesseges]; // Modify this method to process only the new messages
        [self sendEventWithName:MessagesUpdated eventBody:eventBody];
    }
}

-(void) itemsBrowser:(CKItemsBrowser*)browser didRemoveCacheItems:(NSArray*)removedItems atIndexes:(NSIndexSet*)indexes {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self itemsBrowser:browser didRemoveCacheItems:removedItems atIndexes:indexes];
        });
        return;
    }
    NSLog(@"%@ %@", NSStringFromSelector(_cmd), removedItems);
    @synchronized(self.messages){
        NSMutableIndexSet *validatedIndexes = [NSMutableIndexSet indexSet];
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            if(idx < self.messages.count){
                [validatedIndexes addIndex:idx];
            }
        }];
        [self.messages removeObjectsAtIndexes:validatedIndexes];
    }
}

-(void) itemsBrowser:(CKItemsBrowser*)browser didUpdateCacheItems:(NSArray*)changedItems atIndexes:(NSIndexSet*)indexes {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self itemsBrowser:browser didUpdateCacheItems:changedItems atIndexes:indexes];
        });
        return;
    }
    NSLog(@"%@ %@", NSStringFromSelector(_cmd), changedItems);
    //make sync with threads, it prevents different threads from executing the same code at the same time
    @synchronized(self.messages){
        __block NSUInteger changedItemIndex = 0;
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            if (changedItemIndex < self.messages.count && idx < self.messages.count) {
                Message *message = [changedItems objectAtIndex:changedItemIndex];
                [self.messages replaceObjectAtIndex:idx withObject:message];
                changedItemIndex++;
            }

        }];
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        UIApplicationState state = UIApplication.sharedApplication.applicationState;
        if (state == UIApplicationStateBackground || state == UIApplicationStateInactive) {
            UIApplication.sharedApplication.applicationIconBadgeNumber = self->_conversationManagerService.totalNbOfUnreadMessagesInAllConversations;
        }
    });

    [self sendEventWithName:MessagesUpdated eventBody:[self processMesseges]];
}

-(void) itemsBrowser:(CKItemsBrowser*)browser didReorderCacheItemsAtIndexes:(NSArray*)oldIndexes toIndexes:(NSArray*)newIndexes {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self itemsBrowser:browser didReorderCacheItemsAtIndexes:oldIndexes toIndexes:newIndexes];
        });
        return;
    }
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

-(void) itemsBrowser:(CKItemsBrowser *)browser didReceiveItemsAddedEvent:(NSArray *)addedItems {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self itemsBrowser:browser didReceiveItemsAddedEvent:addedItems];
        });
        return;
    }
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

#pragma mark - Composing Notification

-(void) didReceiveComposingMessage:(NSNotification *) notification {
    Message *msg = (Message*) notification.object;
    if ([msg.via isEqual:_currentConversation.peer]) {
        Contact *contact = (Contact *) msg.peer;
        if (msg.isComposing) {
            if (![_typingContacts containsObject:contact])
                [_typingContacts addObject:contact];
        } else {
            [_typingContacts removeObject:contact];
        }
        NSMutableArray *typingContacts = [NSMutableArray new];
        for (Contact *contact in _typingContacts) {
            NSDictionary *dict =  [[NSMutableDictionary alloc] init];
            [dict setValue:contact.contactDictionaryRepresentation forKey:@"contact"];
            [typingContacts addObject:dict];
        }
        [self sendEventWithName:IsTyping eventBody:typingContacts];
    }

}

#pragma mark - Others

-(void)saveDownloadedFile:(File*)downloadedFile {
    if(![NSThread isMainThread]){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self saveDownloadedFile:downloadedFile];
        });
        return;
    }
    //show the activity view for selecting where to download file
    NSString *documentPath = (NSString*)NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    documentPath = [documentPath stringByAppendingString:@"/"];
    NSString *filePath = [documentPath stringByAppendingString:downloadedFile.fileName];
    [downloadedFile.data writeToFile:filePath atomically:YES];
    NSFileManager *fileManager = NSFileManager.defaultManager;
    if ([fileManager fileExistsAtPath:filePath]) {
        NSURL *url = [[NSURL alloc] initFileURLWithPath:filePath];
        UIActivityViewController *controller = [[UIActivityViewController alloc]initWithActivityItems:@[url] applicationActivities:nil];

        [[[RNRainbowRn sharedManager] navigationController]  presentViewController:controller animated:YES completion:nil];
    }
}

-(void)sendFileStatus:(BOOL)success msgDownloadStatus:(NSString*)msg eventName:(NSString*)eventName {
    NSMutableDictionary *downloadFileDic =[[NSMutableDictionary alloc] init];
    [downloadFileDic setValue:[NSNumber numberWithBool:success] forKey:@"success"];
    [downloadFileDic setValue:msg forKey:@"message"];
    [self sendEventWithName:eventName eventBody:downloadFileDic];
}

-(void)searchConversationInMyList:(NSString*)conversationId {
    // All conversations for myUser
    NSArray<Conversation *> *conversationsArray =_conversationManagerService.conversations;
    for(Conversation *conversation in conversationsArray){
        if([conversation.peer.jid isEqualToString:conversationId]){
            _currentConversation = conversation;
            break;
        }
    }
    // If there is no conversation with this peer, create a new one
    if(!self.currentConversation){
        [[ServicesManager sharedInstance].conversationsManagerService startConversationWithPeer:_currentConversation.peer withCompletionHandler:^(Conversation *conversation, NSError *error) {
            if(!error){
                self.currentConversation = conversation;
            } else {
                NSLog(@"[Messages].getMessages Can't create the new conversation, error: %@", [error description]);
            }
        }];
    }
}

-(void)startConversationWithNewConversation:(NSString*)conversationJid{
    
    [[ServicesManager sharedInstance].contactsManagerService searchRainbowContactWithJid:conversationJid withCompletionHandler:^(RainbowContact * _Nullable foundContact) {
        if (foundContact != nil) {
            [[ServicesManager sharedInstance].conversationsManagerService startConversationWithPeer:foundContact withCompletionHandler:^(Conversation *conversation, NSError *error) {
                self->_currentConversation = conversation;
                [self getMessages:conversationJid];
            }];
        }
    }];
  
}

-(MessagePriority)encodeMessageType:(NSString*)msgType{
    if ([msgType  isEqual: @"HIGH"]) {
        return MessagePriorityHigh;
    }
    else if ([msgType  isEqual: @"MEDIUM"]){
        return MessagePriorityMiddle;
    }
    else if ([msgType  isEqual: @"LOW"]){
        return MessagePriorityLow;
    }

    return MessagePriorityDefault;
}

-(NSArray*) processMesseges {
    NSLog(@"[Messages] %@ with count: %lu",NSStringFromSelector(_cmd),(unsigned long)self.messages.count);
    NSMutableArray<NSDictionary *> *array = [[NSMutableArray alloc] init];
    for (int i = 0; i < self.messages.count; i++){
        Message *item = self.messages[i];
        if ([item.replacedMessageID length] > 0) {
            continue;
        }
        if ( item.state == MessageDeliveryStateFailed) {
            continue;
        }
        // We do this just to make a satesfied behaviur between the Android and iOS for sending Attachment
        if ( item.state == MessageDeliveryStateSent && item.attachment != nil) {
            continue;
        }
        [array addObject:item.messageDictionaryRepresentation];
    }
    return array;
}

#pragma mark - Register/UnRegister Notifications

- (void) registerNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didCallSuccess:) name:kConversationsManagerDidUpdateConversation object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveComposingMessage:) name:kConversationsManagerDidReceiveComposingMessage object:nil];
}

- (void) unRegisterNotifications {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kConversationsManagerDidUpdateConversation object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kConversationsManagerDidReceiveComposingMessage object:nil];
}
- ( IEventEmmiter *)eventEmitter {
    if (!_eventEmitter) {
        _eventEmitter = [self.bridge moduleForClass:[IEventEmmiter class]];
    }
    return _eventEmitter;
}

- (void)sendEventWithName:(NSString *)eventName eventBody:(id)eventBody {
    IEventEmmiter *eventEmitter = [self eventEmitter];
    if (eventEmitter) {
        NSLog(@" [Messages] sendEventWithName: %@", eventName);
        [eventEmitter sendEvent:eventName eventBody:eventBody];
    } else {
        NSLog(@"[Messages] sendEventWithName:  Bridge is not ready. Cannot send event: %@", eventName);
    }
}

+ (BOOL)requiresMainQueueSetup
{
    return NO;
}

@end
