import { SubscriptionStatus } from "@prisma/client";
import { botNameAliases } from "../../config/env.js";
import { prisma } from "../../db/prisma.js";
import { sendNativePush } from "../../lib/native-push.js";
import { sendWebPush } from "../../lib/push.js";

type BroadcastInput = {
  title: string;
  body: string;
  url?: string;
  tag?: string;
  renotify?: boolean;
};

type DeliveryContext = {
  notificationType: "signal" | "broadcast";
  title: string;
  body: string;
  signalId?: string;
  tag?: string;
};

type NotifySignalOptions = {
  force?: boolean;
};

type PushSubscriptionRecord = {
  id: string;
  endpoint: string;
  p256dh: string;
  auth: string;
  userId: string;
  active: boolean;
};

type NativePushSubscriptionRecord = {
  id: string;
  token: string;
  platform: string;
  deviceId: string | null;
  userId: string;
  active: boolean;
};

function buildActiveSubscriptionWhere(botKey: string) {
  const now = new Date();

  return {
    status: SubscriptionStatus.active,
    startsAt: {
      lte: now
    },
    OR: [
      {
        expiresAt: null
      },
      {
        expiresAt: {
          gte: now
        }
      }
    ],
    bot: {
      key: botKey
    }
  };
}

function isInactiveSignal(rawPayload: unknown) {
  if (!rawPayload || typeof rawPayload !== "object" || Array.isArray(rawPayload)) {
    return false;
  }

  const payload = rawPayload as Record<string, unknown>;
  return payload.forecastInactive === true || payload.activeTab === 2 || payload.activeTab === "2";
}

function getAliasedBotName(rawPayload: Record<string, unknown> | null) {
  const rawBotKey = typeof rawPayload?.botKey === "string" ? rawPayload.botKey.trim() : "";
  const rawBotName = typeof rawPayload?.botName === "string" ? rawPayload.botName.trim() : "";

  if (rawBotKey) {
    const aliasByKey = botNameAliases.get(rawBotKey);
    if (aliasByKey) {
      return aliasByKey;
    }
  }

  if (rawBotName) {
    const aliasByName = botNameAliases.get(rawBotName);
    if (aliasByName) {
      return aliasByName;
    }
  }

  return rawBotName || "New signal";
}

function getSignalPushPayload(signal: {
  id: string;
  eventId: string;
  homeTeam: string;
  awayTeam: string;
  selection: string;
  odds: unknown;
  forecast: string | null;
  rawPayload: unknown;
}) {
  const rawPayload = signal.rawPayload && typeof signal.rawPayload === "object" && !Array.isArray(signal.rawPayload)
    ? signal.rawPayload as Record<string, unknown>
    : null;
  const botName = getAliasedBotName(rawPayload);
  const teams = [signal.homeTeam, signal.awayTeam]
    .map((team) => team.trim())
    .filter((team) => team.length > 0)
    .join(" - ");
  const forecast = signal.forecast?.trim() || "";
  const odds = typeof signal.odds === "number" && Number.isFinite(signal.odds)
    ? signal.odds.toFixed(2)
    : String(signal.odds).trim();
  const bodyParts = [odds, forecast, botName].filter((part) => part.length > 0);
  const body = bodyParts.join(" - ");

  return {
    title: teams || botName,
    body
  };
}

function getSignalBotKey(signal: { rawPayload: unknown }) {
  if (!signal.rawPayload || typeof signal.rawPayload !== "object" || Array.isArray(signal.rawPayload)) {
    return null;
  }

  const payload = signal.rawPayload as Record<string, unknown>;
  if (typeof payload.botKey !== "string") {
    return null;
  }

  const botKey = payload.botKey.trim();
  return botKey.length > 0 ? botKey : null;
}

function getSignalNotificationFingerprint(signal: {
  eventId: string;
  homeTeam: string;
  awayTeam: string;
  selection: string;
  odds: unknown;
  forecast: string | null;
  rawPayload: unknown;
}) {
  const rawPayload = signal.rawPayload && typeof signal.rawPayload === "object" && !Array.isArray(signal.rawPayload)
    ? signal.rawPayload as Record<string, unknown>
    : null;

  return JSON.stringify({
    eventId: signal.eventId,
    homeTeam: signal.homeTeam,
    awayTeam: signal.awayTeam,
    selection: signal.selection,
    odds: Number(signal.odds),
    forecast: signal.forecast?.trim() || null,
    botName: typeof rawPayload?.botName === "string" ? rawPayload.botName.trim() : null,
    rawForecast: typeof rawPayload?.forecast === "string" ? rawPayload.forecast.trim() : null
  });
}

function getEndpointHost(endpoint: string) {
  try {
    return new URL(endpoint).hostname;
  } catch {
    return "unknown";
  }
}

async function recordDeliveryEvent(
  subscription: PushSubscriptionRecord,
  result:
    | { ok: true; statusCode?: number }
    | { ok: false; reason: string; statusCode?: number; details?: string },
  context: DeliveryContext
) {
  await prisma.integrationLog.create({
    data: {
      provider: "web-push",
      level: result.ok ? "info" : "error",
      message: result.ok ? "Web push delivered" : "Web push delivery failed",
      payload: {
        subscriptionId: subscription.id,
        userId: subscription.userId,
        endpoint: subscription.endpoint,
        endpointHost: getEndpointHost(subscription.endpoint),
        active: subscription.active,
        notificationType: context.notificationType,
        signalId: context.signalId ?? null,
        title: context.title,
        body: context.body,
        tag: context.tag ?? null,
        ok: result.ok,
        statusCode: result.statusCode ?? null,
        reason: result.ok ? null : result.reason,
        details: result.ok ? null : result.details ?? null
      }
    }
  });
}

async function deactivateInvalidSubscription(
  subscription: Pick<PushSubscriptionRecord, "id" | "endpoint" | "userId">,
  result: { ok: false; reason: string; statusCode?: number; details?: string },
  context: DeliveryContext
) {
  if (result.statusCode !== 404 && result.statusCode !== 410) {
    return;
  }

  await prisma.pushSubscription.updateMany({
    where: { id: subscription.id, active: true },
    data: { active: false }
  });

  await prisma.integrationLog.create({
    data: {
      provider: "web-push",
      level: "warn",
      message: "Push subscription deactivated after provider rejection",
      payload: {
        subscriptionId: subscription.id,
        userId: subscription.userId,
        endpoint: subscription.endpoint,
        endpointHost: getEndpointHost(subscription.endpoint),
        notificationType: context.notificationType,
        signalId: context.signalId ?? null,
        tag: context.tag ?? null,
        statusCode: result.statusCode,
        reason: result.reason,
        details: result.details ?? null
      }
    }
  });
}

async function deliverToSubscription(
  subscription: PushSubscriptionRecord,
  payload: Record<string, unknown>,
  context: DeliveryContext
) {
  const result = await sendWebPush(subscription, payload);

  await recordDeliveryEvent(subscription, result, context);

  if (!result.ok) {
    await deactivateInvalidSubscription(subscription, result, context);
  }

  return result;
}

async function recordNativeDeliveryEvent(
  subscription: NativePushSubscriptionRecord,
  result: { ok: true; messageId: string } | { ok: false; reason: string; code?: string },
  context: DeliveryContext
) {
  await prisma.integrationLog.create({
    data: {
      provider: "native-push",
      level: result.ok ? "info" : "error",
      message: result.ok ? "Native push delivered" : "Native push delivery failed",
      payload: {
        subscriptionId: subscription.id,
        userId: subscription.userId,
        platform: subscription.platform,
        deviceId: subscription.deviceId,
        active: subscription.active,
        notificationType: context.notificationType,
        signalId: context.signalId ?? null,
        title: context.title,
        body: context.body,
        tag: context.tag ?? null,
        ok: result.ok,
        code: result.ok ? null : result.code ?? null,
        reason: result.ok ? null : result.reason
      }
    }
  });
}

async function deactivateInvalidNativeSubscription(
  subscription: Pick<NativePushSubscriptionRecord, "id" | "userId" | "token">,
  result: { ok: false; reason: string; code?: string },
  context: DeliveryContext
) {
  if (result.code !== "messaging/registration-token-not-registered") {
    return;
  }

  await prisma.nativePushSubscription.updateMany({
    where: { id: subscription.id, active: true },
    data: { active: false }
  });

  await prisma.integrationLog.create({
    data: {
      provider: "native-push",
      level: "warn",
      message: "Native push subscription deactivated after provider rejection",
      payload: {
        subscriptionId: subscription.id,
        userId: subscription.userId,
        token: subscription.token,
        notificationType: context.notificationType,
        signalId: context.signalId ?? null,
        tag: context.tag ?? null,
        code: result.code ?? null,
        reason: result.reason
      }
    }
  });
}

async function deliverToNativeSubscription(
  subscription: NativePushSubscriptionRecord,
  payload: Record<string, string>,
  context: DeliveryContext
) {
  const result = await sendNativePush(subscription.token, {
    title: context.title,
    body: context.body,
    data: payload
  });

  await recordNativeDeliveryEvent(subscription, result, context);

  if (!result.ok) {
    await deactivateInvalidNativeSubscription(subscription, result, context);
  }

  return result;
}

export async function notifyUsersForSignal(signalId: string, options: NotifySignalOptions = {}) {
  const signal = await prisma.signal.findUnique({ where: { id: signalId } });
  if (!signal) return { recipients: 0, successCount: 0, failedCount: 0 };
  if (isInactiveSignal(signal.rawPayload)) {
    return {
      recipients: 0,
      successCount: 0,
      failedCount: 0,
      skipped: true,
      reason: "inactive_signal"
    };
  }

  const fingerprint = getSignalNotificationFingerprint(signal);
  if (!options.force) {
    const previousNotification = await prisma.notificationLog.findFirst({
      where: {
        signalId,
        type: "signal"
      },
      orderBy: {
        createdAt: "desc"
      }
    });

    const previousPayload = previousNotification?.payload
      && typeof previousNotification.payload === "object"
      && !Array.isArray(previousNotification.payload)
      ? previousNotification.payload as Record<string, unknown>
      : null;

    if (previousPayload?.fingerprint === fingerprint) {
      return {
        recipients: 0,
        successCount: 0,
        failedCount: 0,
        skipped: true,
        reason: "duplicate_signal_notification"
      };
    }
  }

  const botKey = getSignalBotKey(signal);
  const users = await prisma.user.findMany({
    where: {
      active: true,
      notificationSetting: { is: { signalsPushEnabled: true } },
      ...(botKey
        ? {
            OR: [
              {
                role: "admin"
              },
              {
                botAccesses: {
                  some: buildActiveSubscriptionWhere(botKey)
                }
              }
            ]
          }
        : {})
    },
    include: {
      pushSubscriptions: { where: { active: true } },
      nativePushSubscriptions: { where: { active: true } }
    }
  });

  const { title, body } = getSignalPushPayload(signal);
  const tag = `event-${signal.eventId}`;

  let recipients = 0;
  let successCount = 0;
  let failedCount = 0;

  for (const user of users) {
    for (const subscription of user.pushSubscriptions) {
      recipients += 1;

      const result = await deliverToSubscription(
        subscription,
        {
          title,
          body,
          url: `/signals/${signal.id}`,
          tag,
          data: {
            url: `/signals/${signal.id}`,
            eventId: signal.eventId,
            signalId: signal.id,
            type: "signal"
          }
        },
        {
          notificationType: "signal",
          title,
          body,
          signalId: signal.id,
          tag
        }
      );

      if (result.ok) {
        successCount += 1;
      } else {
        failedCount += 1;
      }
    }

    for (const subscription of user.nativePushSubscriptions) {
      recipients += 1;

      const result = await deliverToNativeSubscription(
        subscription,
        {
          url: `/signals/${signal.id}`,
          eventId: signal.eventId,
          signalId: signal.id,
          type: "signal"
        },
        {
          notificationType: "signal",
          title,
          body,
          signalId: signal.id,
          tag
        }
      );

      if (result.ok) {
        successCount += 1;
      } else {
        failedCount += 1;
      }
    }
  }

  await prisma.notificationLog.create({
    data: {
      signalId,
      type: "signal",
      recipients,
      successCount,
      failedCount,
      payload: { title, body, fingerprint }
    }
  });

  return { recipients, successCount, failedCount };
}

export async function broadcastNotification({ title, body, url = "/", tag, renotify = false }: BroadcastInput) {
  const subscriptions = await prisma.pushSubscription.findMany({
    where: { active: true, user: { active: true } }
  });
  const nativeSubscriptions = await prisma.nativePushSubscription.findMany({
    where: { active: true, user: { active: true } }
  });

  let successCount = 0;
  let failedCount = 0;
  const finalTag = tag || `broadcast-${Date.now()}`;

  for (const subscription of subscriptions) {
    const result = await deliverToSubscription(
      subscription,
      {
        title,
        body,
        url,
        tag: finalTag,
        renotify,
        data: {
          url,
          type: "broadcast"
        }
      },
      {
        notificationType: "broadcast",
        title,
        body,
        tag: finalTag
      }
    );

    if (result.ok) {
      successCount += 1;
    } else {
      failedCount += 1;
    }
  }

  for (const subscription of nativeSubscriptions) {
    const result = await deliverToNativeSubscription(
      subscription,
      {
        url,
        type: "broadcast",
        tag: finalTag,
        renotify: String(renotify)
      },
      {
        notificationType: "broadcast",
        title,
        body,
        tag: finalTag
      }
    );

    if (result.ok) {
      successCount += 1;
    } else {
      failedCount += 1;
    }
  }

  await prisma.notificationLog.create({
    data: {
      type: "broadcast",
      recipients: subscriptions.length + nativeSubscriptions.length,
      successCount,
      failedCount,
      payload: { title, body, url, tag: finalTag, renotify }
    }
  });

  return { recipients: subscriptions.length + nativeSubscriptions.length, successCount, failedCount };
}
