/*
 Copyright 2017 Vector Creations Ltd
 Copyright 2019 New Vector Ltd

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import "IntegrationManagerViewController.h"

#import "WidgetManager.h"

#import "GeneratedInterface-Swift.h"

NSString *const kIntegrationManagerMainScreen = nil;
NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ";


@interface IntegrationManagerViewController () <ServiceTermsModalCoordinatorBridgePresenterDelegate>
{
    MXSession *mxSession;
    NSString *roomId;
    NSString *screen;
    NSString *widgetId;
    NSString *scalarToken;

    MXHTTPOperation *operation;
}

@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
@property (nonatomic) BOOL isViewAppearedOnce;

@end

@implementation IntegrationManagerViewController

- (instancetype)initForMXSession:(MXSession *)theMXSession inRoom:(NSString *)theRoomId screen:(NSString *)theScreen widgetId:(NSString *)theWidgetId
{
    self = [super init];
    if (self)
    {
        mxSession = theMXSession;
        roomId = theRoomId;
        screen = theScreen;
        widgetId = theWidgetId;
    }
    return self;
}

+ (NSString*)screenForWidget:(NSString*)widgetType
{
    return [NSString stringWithFormat:@"type_%@", widgetType];
}

- (void)destroy
{
    [super destroy];

    [operation cancel];
    operation = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    if (!self.isViewAppearedOnce)
    {
        self.isViewAppearedOnce = YES;
        [self loadData];
    }
}

- (void)loadData
{
    RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:mxSession];
    if (!sharedSettings.hasIntegrationProvisioningEnabled)
    {
        [self showDisabledIntegrationManagerError];
        return;
    }

    if (!self.URL && !operation)
    {
        [self startActivityIndicator];

        // Make sure we have a scalar token
        MXWeakify(self);
        operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession validate:YES  success:^(NSString *theScalarToken) {
            MXStrongifyAndReturnIfNil(self);

            self->operation = nil;
            self->scalarToken = theScalarToken;

            // Launch the webview on the right modular webapp page
            self.URL = [self interfaceUrl];

        } failure:^(NSError *error) {
            MXStrongifyAndReturnIfNil(self);

            MXLogDebug(@"[IntegraionManagerVS] Cannot open due to missing scalar token. Error: %@", error);

            self->operation = nil;
            [self stopActivityIndicator];

            if ([error.domain isEqualToString:WidgetManagerErrorDomain]
                && error.code == WidgetManagerErrorCodeTermsNotSigned)
            {
                [self presentTerms];
            }
            else
            {
                [self withdrawViewControllerAnimated:YES completion:^{
                    [[AppDelegate theDelegate] showErrorAsAlert:error];
                }];
            }
        }];
    }
}

#pragma mark - Private methods

/**
 Build the URL to use in the Modular interface webapp.
 */
- (NSString *)interfaceUrl
{
    NSMutableString *url;

    NSString *integrationsUiUrl = [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId].uiUrl;

    if (scalarToken)
    {
        url = [NSMutableString stringWithFormat:@"%@?scalar_token=%@&room_id=%@",
               integrationsUiUrl,
               [MXTools encodeURIComponent:scalarToken],
               [MXTools encodeURIComponent:roomId]
               ];

        if (screen)
        {
            [url appendString:@"&screen="];
            [url appendString:[MXTools encodeURIComponent:screen]];
        }

        if (widgetId)
        {
            [url appendString:@"&integ_id="];
            [url appendString:[MXTools encodeURIComponent:widgetId]];
        }
    }
    
    return url;
}

#pragma mark - Modular postMessage API implementation

- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData
{
    NSString *roomIdInEvent, *userId, *action;

    MXJSONModelSetString(roomIdInEvent, requestData[@"room_id"]);
    MXJSONModelSetString(userId, requestData[@"user_id"]);
    MXJSONModelSetString(action, requestData[@"action"]);

    if ([action isEqualToString:@"close_scalar"])
    {
        [self withdrawViewControllerAnimated:YES completion:nil];
        return;
    }

    if (!roomIdInEvent)
    {
        // These APIs don't require roomId
        // Get and set user widgets (not associated with a specific room)
        // If roomId is specified, it must be validated, so room-based widgets agreed
        // handled further down.
        if ([@"set_widget" isEqualToString:action])
        {
            [self setWidget:requestId data:requestData];
            return;
        }
        else if ([@"get_widgets" isEqualToString:action])
        {
            [self getWidgets:requestId data:requestData];
            return;
        }
        else
        {
            [self sendError:[VectorL10n widgetIntegrationMissingRoomId] toRequest:requestId];
            return;
        }
    }

    if (![roomIdInEvent isEqualToString:roomId])
    {
        [self sendError:[VectorL10n widgetIntegrationRoomNotVisible:roomIdInEvent] toRequest:requestId];
        return;
    }

    // Get and set room-based widgets
    if ([@"set_widget" isEqualToString:action])
    {
        [self setWidget:requestId data:requestData];
        return;
    }
    else if ([@"get_widgets" isEqualToString:action])
    {
        [self getWidgets:requestId data:requestData];
        return;
    }

    // These APIs don't require userId
    if ([@"join_rules_state" isEqualToString:action])
    {
        [self getJoinRules:requestId data:requestData];
        return;
    }
    else if ([@"set_plumbing_state" isEqualToString:action])
    {
        [self setPlumbingState:requestId data:requestData];
        return;
    }
    else if ([@"get_membership_count" isEqualToString:action])
    {
        [self getMembershipCount:requestId data:requestData];
        return;
    }
    else if ([@"get_room_enc_state" isEqualToString:action])
    {
        [self getRoomEncState:requestId data:requestData];
        return;
    }
    else if ([@"can_send_event" isEqualToString:action])
    {
        [self canSendEvent:requestId data:requestData];
        return;
    }


    if (!userId)
    {
        [self sendError:[VectorL10n widgetIntegrationMissingUserId] toRequest:requestId];
        return;
    }

    if ([@"membership_state" isEqualToString:action])
    {
        [self getMembershipState:userId request:requestId data:requestData];
    }
    else if ([@"invite" isEqualToString:action])
    {
        [self inviteUser:userId request:requestId data:requestData];
    }
    else if ([@"bot_options" isEqualToString:action])
    {
        [self getBotOptions:userId request:requestId data:requestData];
    }
    else if ([@"set_bot_options" isEqualToString:action])
    {
        [self setBotOptions:userId request:requestId data:requestData];
    }
    else if ([@"set_bot_power" isEqualToString:action])
    {
        [self setBotPower:userId request:requestId data:requestData];
    }
    else
    {
        MXLogDebug(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, requestData);
    }
}

#pragma mark - Private methods

- (void)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData onComplete:(void (^)(MXRoom *room, MXRoomState *roomState))onComplete
{
    MXRoom *room = [mxSession roomWithRoomId:roomId];
    if (room)
    {
        [room state:^(MXRoomState *roomState) {
            onComplete(room, roomState);
        }];
    }
    else
    {
        [self sendError:[VectorL10n widgetIntegrationRoomNotRecognised] toRequest:requestId];
    }
}

- (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {

        MXRoomMember *member = [roomState.members memberWithUserId:userId];
        if (member && member.membership == MXMembershipJoin)
        {
            [self sendNSObjectResponse:@{
                                         @"success": @(YES)
                                         }
                               toRequest:requestId];
        }
        else
        {
            MXWeakify(self);
            [room inviteUser:userId success:^{
                MXStrongifyAndReturnIfNil(self);

                [self sendNSObjectResponse:@{
                                             @"success": @(YES)
                                             }
                                 toRequest:requestId];

            } failure:^(NSError *error) {
                MXStrongifyAndReturnIfNil(self);

                [self sendError:[VectorL10n widgetIntegrationNeedToBeAbleToInvite] toRequest:requestId];
            }];
        }
    }];
}

- (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set widget");

    NSString *widget_id, *widgetType, *widgetUrl;
    NSString *widgetName; // optional
    NSDictionary *widgetData ; // optional
    BOOL userWidget = NO;

    MXJSONModelSetString(widget_id, requestData[@"widget_id"]);
    MXJSONModelSetString(widgetType, requestData[@"type"]);
    MXJSONModelSetString(widgetUrl, requestData[@"url"]);
    MXJSONModelSetString(widgetName, requestData[@"name"]);
    MXJSONModelSetDictionary(widgetData, requestData[@"data"]);
    MXJSONModelSetBoolean(userWidget, requestData[@"userWidget"]);

    if (!widget_id)
    {
        [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
        return;
    }

    if (!widgetType)
    {
        [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
        return;
    }

    NSMutableDictionary *widgetEventContent = [NSMutableDictionary dictionary];
    if (widgetUrl)
    {
        widgetEventContent[@"type"] = widgetType;
        widgetEventContent[@"url"] = widgetUrl;

        if (widgetName)
        {
            widgetEventContent[@"name"] = widgetName;
        }
        if (widgetData)
        {
            widgetEventContent[@"data"] = widgetData;
        }
    }
    // else this is a deletion

    __weak __typeof__(self) weakSelf = self;

    if (userWidget)
    {
        // Update the user account data
        NSMutableDictionary *userWidgets = [NSMutableDictionary dictionaryWithDictionary:[mxSession.accountData accountDataForEventType:kMXAccountDataTypeUserWidgets]];

        // Delete existing widget with ID
        [userWidgets removeObjectForKey:widget_id];

        // Add new widget / update
        if (widgetUrl)
        {
            userWidgets[widget_id] = @{
                                      @"content": widgetEventContent,
                                      @"sender": mxSession.myUser.userId,
                                      @"state_key": widget_id,
                                      @"type": kWidgetMatrixEventTypeString,
                                      @"id": widget_id,
                                      };
        }

        [mxSession setAccountData:userWidgets forType:kMXAccountDataTypeUserWidgets success:^{

            typeof(self) self = weakSelf;
            if (self)
            {
                [self sendNSObjectResponse:@{
                                             @"success": @(YES)
                                             }
                                 toRequest:requestId];
            }
        } failure:^(NSError *error) {

            typeof(self) self = weakSelf;
            if (self)
            {
                [self sendError:[VectorL10n widgetIntegrationUnableToCreate] toRequest:requestId];
            }
        }];
    }
    else
    {
        // Room widget
        [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {

            // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when?
            [room sendStateEventOfType:kWidgetModularEventTypeString
                               content:widgetEventContent
                              stateKey:widget_id
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }];
    }
}

- (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXWeakify(self);
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXStrongifyAndReturnIfNil(self);

        NSMutableArray<NSDictionary*> *widgetStateEvents = [NSMutableArray array];

        NSArray<Widget*> *widgets = [[WidgetManager sharedManager] widgetsInRoom:room withRoomState:roomState];
        for (Widget *widget in widgets)
        {
            [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
        }

        // Add user widgets (not linked to a specific room)
        for (Widget *widget in [[WidgetManager sharedManager] userWidgets:self->mxSession])
        {
            [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary];
        }

        [self sendNSObjectResponse:widgetStateEvents toRequest:requestId];
    }];
}

- (void)getRoomEncState:(NSString*)requestId data:(NSDictionary*)requestData
{
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        [self sendBoolResponse:room.summary.isEncrypted toRequest:requestId];
    }];
}

- (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXWeakify(self);
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXStrongifyAndReturnIfNil(self);

        NSString *eventType;
        BOOL isState = NO;

        if (room.summary.membership != MXMembershipJoin)
        {
            [self sendError:[VectorL10n widgetIntegrationMustBeInRoom] toRequest:requestId];
            return;
        }

        MXJSONModelSetString(eventType, requestData[@"event_type"]);
        MXJSONModelSetBoolean(isState, requestData[@"is_state"]);

        MXRoomPowerLevels *powerLevels = roomState.powerLevels;
        NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:self->mxSession.myUser.userId];

        BOOL canSend = NO;

        if (isState)
        {
            canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsStateEvent:eventType]);
        }
        else
        {
            canSend = (userPowerLevel >= [powerLevels minimumPowerLevelForSendingEventAsMessage:eventType]);
        }

        if (canSend)
        {
            [self sendBoolResponse:YES toRequest:requestId];
        }
        else
        {
            [self sendError:[VectorL10n widgetIntegrationNoPermissionInRoom] toRequest:requestId];
        }

    }];
}

- (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXRoomMember *member = [roomState.members memberWithUserId:userId];
        [self sendNSObjectResponse:member.originalEvent.content toRequest:requestId];
    }];
}

- (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        MXEvent *event = [roomState stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject;
        [self sendNSObjectResponse:event.JSONDictionary toRequest:requestId];
    }];
}

- (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSString *status;
        MXJSONModelSetString(status, requestData[@"status"]);

        if (status)
        {
            __weak __typeof__(self) weakSelf = self;

            [room sendStateEventOfType:kMXEventTypeStringRoomPlumbing
                               content:@{
                                         @"status": status
                                         }
                              stateKey:nil
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setPlumbingState. Error: Plumbing state status should be a string.");
        }

    }];
}

- (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];

        NSArray<MXEvent*> *stateEvents = [roomState stateEventsWithType:kMXEventTypeStringRoomBotOptions];

        MXEvent *botOptionsEvent;

        for (MXEvent *stateEvent in stateEvents)
        {
            if ([stateEvent.stateKey isEqualToString:stateKey])
            {
                if (!botOptionsEvent || stateEvent.ageLocalTs > botOptionsEvent.ageLocalTs)
                {
                    botOptionsEvent = stateEvent;
                }
            }
        }

        [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId];

    }];
}

- (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSDictionary *content;
        MXJSONModelSetDictionary(content, requestData[@"content"]);

        if (content)
        {
            __weak __typeof__(self) weakSelf = self;

            NSString *stateKey = [NSString stringWithFormat:@"_%@", userId];

            [room sendStateEventOfType:kMXEventTypeStringRoomBotOptions
                               content:content
                              stateKey:stateKey
                               success:^(NSString *eventId) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendNSObjectResponse:@{
                                                                    @"success": @(YES)
                                                                    }
                                                        toRequest:requestId];
                                   }
                               }
                               failure:^(NSError *error) {

                                   typeof(self) self = weakSelf;
                                   if (self)
                                   {
                                       [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                                   }
                               }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setBotOptions. Error: options should be a dict.");
        }
    }];
}

- (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData
{
    MXLogDebug(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId);

    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSInteger level = -1;
        MXJSONModelSetInteger(level, requestData[@"level"]);

        if (level >= 0)
        {
            __weak __typeof__(self) weakSelf = self;

            [room setPowerLevelOfUserWithUserID:userId powerLevel:level success:^{

                typeof(self) self = weakSelf;
                if (self)
                {
                    [self sendNSObjectResponse:@{
                                                 @"success": @(YES)
                                                 }
                                     toRequest:requestId];
                }

            } failure:^(NSError *error) {

                typeof(self) self = weakSelf;
                if (self)
                {
                    [self sendError:[VectorL10n widgetIntegrationFailedToSendRequest] toRequest:requestId];
                }
            }];
        }
        else
        {
            MXLogDebug(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer.");
            [self sendError:[VectorL10n widgetIntegrationPositivePowerLevel] toRequest:requestId];
        }
    }];
}

- (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData
{
    [self roomCheckForRequest:requestId data:requestData onComplete:^(MXRoom *room, MXRoomState *roomState) {
        NSUInteger membershipCount = room.summary.membersCount.joined;
        [self sendIntegerResponse:membershipCount toRequest:requestId];
    }];
}


#pragma mark - Widget Permission

- (void)checkWidgetPermissionWithCompletion:(void (^)(BOOL granted))completion
{
    // The integration manager widget has its own terms
    completion(YES);
}


#pragma mark - Disabled Integrations

- (void)showDisabledIntegrationManagerError
{
    NSString *message = [VectorL10n widgetIntegrationManagerDisabled];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
                                                                   message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];

    [alert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok]
                                              style:UIAlertActionStyleDefault
                                            handler:^(UIAlertAction * action) {
                                                [self withdrawViewControllerAnimated:YES completion:nil];
                                            }]];

    [self presentViewController:alert animated:YES completion:nil];
}

#pragma mark - Service terms

- (void)presentTerms
{
    WidgetManagerConfig *config =  [[WidgetManager sharedManager] configForUser:mxSession.myUser.userId];

    MXLogDebug(@"[IntegrationManagerVC] presentTerms for %@", config.baseUrl);

    ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:config.baseUrl
                                                                                                                                                        serviceType:MXServiceTypeIntegrationManager
                                                                                                                                                        accessToken:config.scalarToken];

    serviceTermsModalCoordinatorBridgePresenter.delegate = self;

    [serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES];
    self.serviceTermsModalCoordinatorBridgePresenter = serviceTermsModalCoordinatorBridgePresenter;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
    [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
        [self loadData];
    }];
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession * _Nonnull)session
{
    [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{
        [self withdrawViewControllerAnimated:YES completion:nil];
    }];
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

- (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter
{
    self.serviceTermsModalCoordinatorBridgePresenter = nil;
}

@end
