import { IssueStatus, IssueType } from '@server/constants/issue';
import { MediaStatus } from '@server/constants/media';
import type { NotificationAgentWebhook } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import axios from 'axios';
import { get } from 'lodash';
import { Notification, hasNotificationType } from '..';
import type { NotificationAgent, NotificationPayload } from './agent';
import { BaseAgent } from './agent';

type KeyMapFunction = (
  payload: NotificationPayload,
  type: Notification
) => string;

const KeyMap: Record<string, string | KeyMapFunction> = {
  notification_type: (_payload, type) => Notification[type],
  event: 'event',
  subject: 'subject',
  message: 'message',
  image: 'image',
  notifyuser_username: 'notifyUser.displayName',
  notifyuser_email: 'notifyUser.email',
  notifyuser_avatar: 'notifyUser.avatar',
  notifyuser_settings_discordId: 'notifyUser.settings.discordId',
  notifyuser_settings_telegramChatId: 'notifyUser.settings.telegramChatId',
  media_imdbid: 'media.imdbId',
  media_tmdbid: 'media.tmdbId',
  media_tvdbid: 'media.tvdbId',
  media_type: 'media.mediaType',
  media_jellyfinMediaId: (payload) =>
    payload.media?.jellyfinMediaId ?? payload.media?.jellyfinMediaId4k ?? '',
  media_status: (payload) =>
    payload.media ? MediaStatus[payload.media.status] : '',
  media_status4k: (payload) =>
    payload.media ? MediaStatus[payload.media.status4k] : '',
  request_id: 'request.id',
  requestedBy_jellyfinUserId: 'request.requestedBy.jellyfinUserId',
  requestedBy_username: 'request.requestedBy.displayName',
  requestedBy_email: 'request.requestedBy.email',
  requestedBy_avatar: 'request.requestedBy.avatar',
  requestedBy_settings_discordId: 'request.requestedBy.settings.discordId',
  requestedBy_settings_telegramChatId:
    'request.requestedBy.settings.telegramChatId',
  issue_id: 'issue.id',
  issue_type: (payload) =>
    payload.issue ? IssueType[payload.issue.issueType] : '',
  issue_status: (payload) =>
    payload.issue ? IssueStatus[payload.issue.status] : '',
  reportedBy_username: 'issue.createdBy.displayName',
  reportedBy_email: 'issue.createdBy.email',
  reportedBy_avatar: 'issue.createdBy.avatar',
  reportedBy_settings_discordId: 'issue.createdBy.settings.discordId',
  reportedBy_settings_telegramChatId: 'issue.createdBy.settings.telegramChatId',
  comment_message: 'comment.message',
  commentedBy_username: 'comment.user.displayName',
  commentedBy_email: 'comment.user.email',
  commentedBy_avatar: 'comment.user.avatar',
  commentedBy_settings_discordId: 'comment.user.settings.discordId',
  commentedBy_settings_telegramChatId: 'comment.user.settings.telegramChatId',
};

class WebhookAgent
  extends BaseAgent<NotificationAgentWebhook>
  implements NotificationAgent
{
  protected getSettings(): NotificationAgentWebhook {
    if (this.settings) {
      return this.settings;
    }

    const settings = getSettings();

    return settings.notifications.agents.webhook;
  }

  private parseKeys(
    finalPayload: Record<string, unknown>,
    payload: NotificationPayload,
    type: Notification
  ): Record<string, unknown> {
    Object.keys(finalPayload).forEach((key) => {
      if (key === '{{extra}}') {
        finalPayload.extra = payload.extra ?? [];
        delete finalPayload[key];
        key = 'extra';
      } else if (key === '{{media}}') {
        if (payload.media) {
          finalPayload.media = finalPayload[key];
        } else {
          finalPayload.media = null;
        }
        delete finalPayload[key];
        key = 'media';
      } else if (key === '{{request}}') {
        if (payload.request) {
          finalPayload.request = finalPayload[key];
        } else {
          finalPayload.request = null;
        }
        delete finalPayload[key];
        key = 'request';
      } else if (key === '{{issue}}') {
        if (payload.issue) {
          finalPayload.issue = finalPayload[key];
        } else {
          finalPayload.issue = null;
        }
        delete finalPayload[key];
        key = 'issue';
      } else if (key === '{{comment}}') {
        if (payload.comment) {
          finalPayload.comment = finalPayload[key];
        } else {
          finalPayload.comment = null;
        }
        delete finalPayload[key];
        key = 'comment';
      }

      if (typeof finalPayload[key] === 'string') {
        Object.keys(KeyMap).forEach((keymapKey) => {
          const keymapValue = KeyMap[keymapKey as keyof typeof KeyMap];
          finalPayload[key] = (finalPayload[key] as string).replace(
            `{{${keymapKey}}}`,
            typeof keymapValue === 'function'
              ? keymapValue(payload, type)
              : (get(payload, keymapValue) ?? '')
          );
        });
      } else if (finalPayload[key] && typeof finalPayload[key] === 'object') {
        finalPayload[key] = this.parseKeys(
          finalPayload[key] as Record<string, unknown>,
          payload,
          type
        );
      }
    });

    return finalPayload;
  }

  private buildPayload(type: Notification, payload: NotificationPayload) {
    const payloadString = Buffer.from(
      this.getSettings().options.jsonPayload,
      'base64'
    ).toString('utf8');

    const parsedJSON = JSON.parse(JSON.parse(payloadString));

    return this.parseKeys(parsedJSON, payload, type);
  }

  public shouldSend(): boolean {
    const settings = this.getSettings();

    if (settings.enabled && settings.options.webhookUrl) {
      return true;
    }

    return false;
  }

  public async send(
    type: Notification,
    payload: NotificationPayload
  ): Promise<boolean> {
    const settings = this.getSettings();

    if (
      !payload.notifySystem ||
      !hasNotificationType(type, settings.types ?? 0)
    ) {
      return true;
    }

    logger.debug('Sending webhook notification', {
      label: 'Notifications',
      type: Notification[type],
      subject: payload.subject,
    });

    let webhookUrl = settings.options.webhookUrl;

    if (settings.options.supportVariables) {
      Object.keys(KeyMap).forEach((keymapKey) => {
        const keymapValue = KeyMap[keymapKey as keyof typeof KeyMap];
        const variableValue =
          type === Notification.TEST_NOTIFICATION
            ? 'test'
            : typeof keymapValue === 'function'
              ? keymapValue(payload, type)
              : get(payload, keymapValue) || 'test';
        webhookUrl = webhookUrl.replace(
          new RegExp(`{{${keymapKey}}}`, 'g'),
          encodeURIComponent(variableValue)
        );
      });
    }

    try {
      const headers: Record<string, string> = {};

      if (settings.options.authHeader) {
        headers.Authorization = settings.options.authHeader;
      }

      if (
        settings.options.customHeaders &&
        settings.options.customHeaders.length > 0
      ) {
        settings.options.customHeaders.forEach((header) => {
          const key = header.key?.trim();
          const value = header.value?.trim();

          if (key && value) {
            // Don't override Authorization header if it's already set via authHeader
            if (
              key.toLowerCase() !== 'authorization' ||
              !settings.options.authHeader
            ) {
              headers[key] = value;
            }
          }
        });
      }

      await axios.post(
        webhookUrl,
        this.buildPayload(type, payload),
        Object.keys(headers).length > 0 ? { headers } : undefined
      );

      return true;
    } catch (e) {
      logger.error('Error sending webhook notification', {
        label: 'Notifications',
        type: Notification[type],
        subject: payload.subject,
        errorMessage: e.message,
        response: e?.response?.data,
      });

      return false;
    }
  }
}

export default WebhookAgent;
