/*
 *   Famedly Matrix SDK
 *   Copyright (C) 2019, 2020 Famedly GmbH
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as
 *   published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see .
 */
import 'dart:convert';
import 'package:test/test.dart';
import 'package:matrix/matrix.dart';
import 'fake_client.dart';
void _testMatch(PushRuleSet ruleset, Event event) {
  final evaluator = PushruleEvaluator.fromRuleset(ruleset);
  final actions = evaluator.match(event);
  expect(actions.notify, true);
  expect(actions.highlight, true);
  expect(actions.sound, 'goose.wav');
}
void _testNotMatch(PushRuleSet ruleset, Event event) {
  final evaluator = PushruleEvaluator.fromRuleset(ruleset);
  final actions = evaluator.match(event);
  expect(actions.notify, false);
  expect(actions.highlight, false);
  expect(actions.sound, null);
}
void main() {
  /// All Tests related to the Event
  group('Event', () {
    Logs().level = Level.error;
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    final id = '!4fsdfjisjf:server.abc';
    final senderID = '@alice:server.abc';
    final type = 'm.room.message';
    final msgtype = 'm.text';
    final body = 'Hello fox';
    final formatted_body = 'Hello fox';
    final contentJson =
        '{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body","m.relates_to":{"m.in_reply_to":{"event_id":"\$1234:example.com"}}}';
    final jsonObj = {
      'event_id': id,
      'sender': senderID,
      'origin_server_ts': timestamp,
      'type': type,
      'room_id': '!testroom:example.abc',
      'status': EventStatus.synced.intValue,
      'content': json.decode(contentJson),
    };
    late Client client;
    late Room room;
    setUpAll(() async {
      client = await getClient();
      room = Room(id: '!testroom:example.abc', client: client);
    });
    test('event_match rule', () async {
      final event = Event.fromJson(jsonObj, room);
      final override_ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'event_match',
                pattern: 'fox',
                key: 'content.body',
              ),
            ],
          ),
        ],
      );
      final underride_ruleset = PushRuleSet(
        underride: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'event_match',
                pattern: 'fox',
                key: 'content.body',
              ),
            ],
          ),
        ],
      );
      final content_ruleset = PushRuleSet(
        content: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            pattern: 'fox',
          ),
        ],
      );
      final room_ruleset = PushRuleSet(
        room: [
          PushRule(
            ruleId: room.id,
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
          ),
        ],
      );
      final sender_ruleset = PushRuleSet(
        sender: [
          PushRule(
            ruleId: senderID,
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
          ),
        ],
      );
      _testMatch(override_ruleset, event);
      _testMatch(underride_ruleset, event);
      _testMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      event.content['body'] = 'FoX';
      _testMatch(override_ruleset, event);
      _testMatch(underride_ruleset, event);
      _testMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      event.content['body'] = '@FoX:';
      _testMatch(override_ruleset, event);
      _testMatch(underride_ruleset, event);
      _testMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      event.content['body'] = 'äFoXü';
      _testMatch(override_ruleset, event);
      _testMatch(underride_ruleset, event);
      _testMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      event.content['body'] = 'äFoXu';
      _testNotMatch(override_ruleset, event);
      _testNotMatch(underride_ruleset, event);
      _testNotMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      event.content['body'] = 'aFoXü';
      _testNotMatch(override_ruleset, event);
      _testNotMatch(underride_ruleset, event);
      _testNotMatch(content_ruleset, event);
      _testMatch(room_ruleset, event);
      _testMatch(sender_ruleset, event);
      final override_ruleset2 = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'event_match',
                pattern: senderID,
                key: 'sender',
              ),
            ],
          ),
        ],
      );
      _testMatch(override_ruleset2, event);
      event.senderId = '@nope:server.tld';
      _testNotMatch(override_ruleset2, event);
      event.senderId = '${senderID}a';
      _testNotMatch(override_ruleset2, event);
      event.senderId = 'a$senderID';
      _testNotMatch(override_ruleset2, event);
      event.senderId = senderID;
      _testMatch(override_ruleset2, event);
      override_ruleset2.override?[0].enabled = false;
      _testNotMatch(override_ruleset2, event);
    });
    test('event_property_is rule', () async {
      final event = Event.fromJson(jsonObj, room);
      event.content['body'] = 'Hello fox';
      final ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'event_property_is',
                key: 'content.body',
                value: 'Hello fox',
              ),
            ],
          ),
        ],
      );
      _testMatch(ruleset, event);
      event.content['body'] = 'Hello Fox';
      _testNotMatch(ruleset, event);
      event.content['body'] = null;
      ruleset.override?[0].conditions?[0].value = null;
      _testMatch(ruleset, event);
      event.content['body'] = true;
      _testNotMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].value = true;
      _testMatch(ruleset, event);
      event.content['body'] = 12345;
      _testNotMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].value = 12345;
      _testMatch(ruleset, event);
    });
    test('event_property_contains rule', () async {
      final event = Event.fromJson(jsonObj, room);
      final ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'event_property_contains',
                key: 'content.body',
                value: 'Fox',
              ),
            ],
          ),
        ],
      );
      _testNotMatch(ruleset, event);
      event.content['body'] = [];
      _testNotMatch(ruleset, event);
      event.content['body'] = null;
      _testNotMatch(ruleset, event);
      event.content['body'] = ['Fox'];
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].value = true;
      _testNotMatch(ruleset, event);
      event.content['body'] = [12345, true];
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].value = 12345;
      _testMatch(ruleset, event);
    });
    test('match_display_name rule', () async {
      final event = Event.fromJson(jsonObj, room);
      (event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
          Event.fromJson(
        {
          'type': EventTypes.RoomMember,
          'sender': senderID,
          'state_key': 'client.senderID',
          'content': {'displayname': 'Nico', 'membership': 'join'},
          'room_id': room.id,
          'origin_server_ts': 5,
        },
        room,
      );
      final ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(kind: 'contains_display_name'),
            ],
          ),
        ],
      );
      event.content['body'] = 'äNicoü';
      _testMatch(ruleset, event);
      event.content['body'] = 'äNicou';
      _testNotMatch(ruleset, event);
    });
    test('member_count rule', () async {
      final event = Event.fromJson(jsonObj, room);
      (event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
          Event.fromJson(
        {
          'type': EventTypes.RoomMember,
          'sender': senderID,
          'state_key': 'client.senderID',
          'content': {'displayname': 'Nico', 'membership': 'join'},
          'room_id': room.id,
          'origin_server_ts': 5,
        },
        room,
      );
      final ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(kind: 'room_member_count', is$: '<5'),
            ],
          ),
        ],
      );
      event.content['body'] = 'äNicoü';
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '<=0';
      _testNotMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '<=1';
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '>=1';
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '>1';
      _testNotMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '==1';
      _testMatch(ruleset, event);
      ruleset.override?[0].conditions?[0].is$ = '1';
      _testMatch(ruleset, event);
    });
    test('notification permissions rule', () async {
      final event = Event.fromJson(jsonObj, room);
      (event.room.states[EventTypes.RoomPowerLevels] ??= {})[''] =
          Event.fromJson(
        {
          'type': EventTypes.RoomMember,
          'sender': senderID,
          'state_key': 'client.senderID',
          'content': {
            'notifications': {'broom': 20},
            'users': {senderID: 20},
          },
          'room_id': room.id,
          'origin_server_ts': 5,
        },
        room,
      );
      final ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'sender_notification_permission',
                key: 'broom',
              ),
            ],
          ),
        ],
      );
      _testMatch(ruleset, event);
      event.senderId = '@a:b.c';
      _testNotMatch(ruleset, event);
    });
    test('invalid push condition', () async {
      final invalid_ruleset = PushRuleSet(
        override: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            conditions: [
              PushCondition(
                kind: 'invalidcondition',
                pattern: 'fox',
                key: 'content.body',
              ),
            ],
          ),
        ],
      );
      expect(
        () => PushruleEvaluator.fromRuleset(invalid_ruleset),
        returnsNormally,
      );
      final event = Event.fromJson(jsonObj, room);
      _testNotMatch(invalid_ruleset, event);
    });
    test('invalid content rule', () async {
      final invalid_content_ruleset = PushRuleSet(
        content: [
          PushRule(
            ruleId: 'my.rule',
            default$: false,
            enabled: true,
            actions: [
              'notify',
              {'set_tweak': 'highlight', 'value': true},
              {'set_tweak': 'sound', 'value': 'goose.wav'},
            ],
            // pattern: 'fox', <- no pattern!
          ),
        ],
      );
      expect(
        () => PushruleEvaluator.fromRuleset(invalid_content_ruleset),
        returnsNormally,
      );
      final dendriteRuleset = PushRuleSet.fromJson(
        json.decode('''{
  "global": {
    "override": [
      {
        "rule_id": ".m.rule.master",
        "default": true,
        "enabled": false,
        "actions": [
          "dont_notify"
        ],
        "conditions": [],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.suppress_notices",
        "default": true,
        "enabled": true,
        "actions": [
          "dont_notify"
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "content.msgtype",
            "pattern": "m.notice"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.invite_for_me",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "sound",
            "value": "default"
          },
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.member"
          },
          {
            "kind": "event_match",
            "key": "content.membership",
            "pattern": "invite"
          },
          {
            "kind": "event_match",
            "key": "state_key",
            "pattern": "@deepbluev7:dendrite.matrix.org"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.member_event",
        "default": true,
        "enabled": true,
        "actions": [
          "dont_notify"
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.member"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.contains_display_name",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "sound",
            "value": "default"
          },
          {
            "set_tweak": "highlight",
            "value": true
          }
        ],
        "conditions": [
          {
            "kind": "contains_display_name"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.tombstone",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.tombstone"
          },
          {
            "kind": "event_match",
            "key": "state_key"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.roomnotif",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "content.body",
            "pattern": "@room"
          },
          {
            "kind": "sender_notification_permission",
            "key": "room"
          }
        ],
        "pattern": ""
      }
    ],
    "content": [
      {
        "rule_id": ".m.rule.contains_user_name",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "sound",
            "value": "default"
          },
          {
            "set_tweak": "highlight",
            "value": true
          }
        ],
        "conditions": null,
        "pattern": "deepbluev7"
      }
    ],
    "underride": [
      {
        "rule_id": ".m.rule.call",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "sound",
            "value": "ring"
          },
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.call.invite"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.encrypted_room_one_to_one",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "room_member_count",
            "is": "2"
          },
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.encrypted"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.room_one_to_one",
        "default": true,
        "enabled": true,
        "actions": [
          "notify",
          {
            "set_tweak": "highlight",
            "value": false
          }
        ],
        "conditions": [
          {
            "kind": "room_member_count",
            "is": "2"
          },
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.message"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.message",
        "default": true,
        "enabled": true,
        "actions": [
          "notify"
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.message"
          }
        ],
        "pattern": ""
      },
      {
        "rule_id": ".m.rule.encrypted",
        "default": true,
        "enabled": true,
        "actions": [
          "notify"
        ],
        "conditions": [
          {
            "kind": "event_match",
            "key": "type",
            "pattern": "m.room.encrypted"
          }
        ],
        "pattern": ""
      }
    ]
  }
}
'''),
      );
      expect(
        () => PushruleEvaluator.fromRuleset(dendriteRuleset),
        returnsNormally,
      );
    });
  });
}