content: /*
   * Kiwii, a stupid Discord bot.
   * Copyright (C) 2019-2024 Lexedia
   * 
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU 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 General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
   */
  
  import 'package:darq/darq.dart';
  import 'package:nyxx/nyxx.dart' hide Cache;
  import 'package:nyxx_extensions/nyxx_extensions.dart';
  import 'package:sentry/sentry.dart';
  
  import '../kiwii.dart';
  import '../plugins/localization.dart';
  import '../src/moderation/case/delete_case.dart';
  import '../utils/extensions.dart';
  import '../src/models/case.dart';
  import '../src/moderation/case/create_case.dart';
  import '../src/moderation/replies/acknowledge_case.dart';
  
  Future<void> onAutoModerationActionExecutionTimeout(AutoModerationActionExecutionEvent event) async {
    try {
      if (event.action.type != ActionType.timeout) {
        return;
      }
  
      final guild = await event.guild.get();
  
      final client = event.guild.manager.client;
  
      final user = await event.user.get();
  
      await client.selfCache['guild${guild.id}:user:${user.id}:automodtimeout'].set('', const Duration(seconds: 15));
  
      final reason = switch (event.triggerType) {
        TriggerType.keyword => guild.t.moderation.logs.autoMod.keyword,
        TriggerType.keywordPreset => guild.t.moderation.logs.autoMod.keywordPreset,
        TriggerType.mentionSpam => guild.t.moderation.logs.autoMod.mentionSpam,
        TriggerType.spam => guild.t.moderation.logs.autoMod.spam,
        TriggerType() => throw UnimplementedError(),
      };
  
      final ccase = await createCase(
        guild,
        CreateCase(
          guildId: guild.id,
          action: CaseAction.timeout,
          targetId: user.id,
          targetTag: user.tag,
          reason: reason,
          duration: event.action.metadata?.duration,
          modId: guild.manager.client.user.id,
          modTag: (await guild.manager.client.user.get()).tag,
        ),
        skip: true,
      );
  
      await acknowledgeCase(guild, ccase, '/', await guild.manager.client.user.get());
    } catch (e) {
      Sentry.logger.fmt.warn(
        'Failed create case for timeout action',
        [],
        attributes: {'guildId': SentryLogAttribute.string(event.guildId.toString()), 'userId': SentryLogAttribute.string(event.userId.toString())},
      );
    }
  }
  
  Future<void> onGuildMemberUpdateTimeout(GuildMemberUpdateEvent event) async {
    try {
      final GuildMemberUpdateEvent(:oldMember, member: newMember) = event;
  
      final client = event.guild.manager.client;
  
      final guildSettings = await client.repositories.guilds.getOrNull(event.guildId);
  
      if (guildSettings == null) {
        return;
      }
  
      final guild = await event.guild.get();
  
      if (guildSettings.modLogChannelId == null || oldMember?.communicationDisabledUntil == newMember.communicationDisabledUntil) {
        return;
      }
  
      final deleted = await client.selfCache['guild:${event.guildId}:user:${newMember.id}:timeout'].get();
  
      // If null, either the user was never timed out or the TTL expired.
      if (deleted != null) {
        await client.selfCache['guild:${event.guildId}:user:${newMember.id}:timeout'].purge();
        return;
      }
  
      // Wait to prevent collisions.
      await Future<void>.delayed(const Duration(seconds: 2, milliseconds: 500));
  
      final autoMod = await client.selfCache['guild:${event.guildId}:user:${newMember.id}:automodtimeout'].get();
  
      // Same as above.
      if (autoMod != null) {
        await client.selfCache['guild:${event.guildId}:user:${newMember.id}:automodtimeout'].purge();
        return;
      }
  
      final auditLogs = await event.guild.auditLogs.list(limit: 10, type: AuditLogEvent.memberUpdate);
  
      final logs = auditLogs.firstWhereOrDefault(
        (e) => e.targetId == newMember.id && e.changes?.any((c) => c.key == 'communication_disabled_until') == true,
        defaultValue: null,
      );
  
      if (logs == null || logs.changes == null || logs.changes?.isEmpty == true) {
        return;
      }
  
      final timeoutChanges = logs.changes!.firstWhereOrDefault((value) => value.key == 'communication_disabled_until', defaultValue: null);
  
      if (timeoutChanges == null) {
        return;
      }
  
      final oldVal = timeoutChanges.oldValue as String?;
      final newVal = timeoutChanges.newValue as String?;
  
      final hasTimeoutEnded = (oldVal != null && oldVal.isNotEmpty) && (newVal == null || newVal.isEmpty);
  
      final user = await logs.user?.get();
      User? target;
      if (logs.targetId != null) {
        target = await client.users.get(logs.targetId!);
      }
  
      final ccase =
          hasTimeoutEnded
              ? await deleteCase(
                DeleteCase(
                  guildId: event.guildId,
                  modId: logs.userId,
                  modTag: user?.tag,
                  targetId: logs.targetId,
                  targetTag: target?.tag,
                  reason: logs.reason,
                  action: CaseAction.timeout,
                ),
                await event.guild.get(),
                isManual: true,
                shouldSkip: true,
              )
              : await createCase(
                (await event.guild.get()),
                CreateCase(
                  guildId: event.guildId,
                  action: CaseAction.timeout,
                  targetId: target!.id,
                  targetTag: target.tag,
                  duration: (newMember.communicationDisabledUntil ?? DateTime.now()).difference(DateTime.now()),
                  modId: logs.userId,
                  modTag: user?.tag,
                  reason: logs.reason,
                ),
                skip: true,
              );
  
      await acknowledgeCase(guild, ccase, '/', user);
    } catch (e) {
      Sentry.logger.fmt.warn(
        'Failed to update timeout for user %s',
        [event.member.id],
        attributes: {'guildId': SentryLogAttribute.string(event.guildId.toString()), 'userId': SentryLogAttribute.string(event.member.id.toString())},
      );
    }
  }
  