/**
 * Notification Service
 *
 * Handles all notification-related operations including:
 * - Creating notifications for user actions
 * - Retrieving notifications with filtering
 * - Marking notifications as read
 * - Checking for upcoming contract renewals/deadlines
 * - Cleaning up expired notifications
 *
 * Integrates with Prisma ORM for database operations.
 * Supports multiple notification types: SUCCESS, WARNING, ERROR, INFO, DEADLINE
 */

import { prisma } from "@/lib/db/prisma";
import { EmailService } from "@/lib/services/email.service";

let hasWarnedMissingNotificationTable = false;

const isNotificationTableMissingError = (error: unknown): boolean => {
  if (!error || typeof error !== "object") return false;

  const maybePrismaError = error as {
    code?: string;
    meta?: { table?: string };
    message?: string;
  };

  if (maybePrismaError.code !== "P2021") return false;

  const tableFromMeta = maybePrismaError.meta?.table ?? "";
  const message = maybePrismaError.message ?? "";

  return (
    tableFromMeta.includes("Notification") ||
    message.includes("public.Notification")
  );
};

const warnMissingNotificationTableOnce = () => {
  if (hasWarnedMissingNotificationTable) return;
  hasWarnedMissingNotificationTable = true;
  console.warn(
    "Notification table is missing. Notification features are temporarily disabled until schema is synced.",
  );
};

/**
 * Notification type for creating new notifications
 */
interface CreateNotificationInput {
  userId: string;
  type: "SUCCESS" | "WARNING" | "ERROR" | "INFO" | "DEADLINE";
  title: string;
  message: string;
  contractId?: string;
  actionType?: string;
  actionData?: Record<string, any>;
  icon?: string;
  expiresIn?: number; // milliseconds
}

/**
 * Response type for notification operations
 */
interface NotificationResponse {
  success: boolean;
  message?: string;
  data?: any;
  error?: string;
}

export class NotificationService {
  static async cleanupReadNonDeadline(
    userId: string,
  ): Promise<NotificationResponse> {
    try {
      const result = await prisma.notification.deleteMany({
        where: {
          userId,
          read: true,
          NOT: {
            type: "DEADLINE",
          },
        },
      });

      return {
        success: true,
        message: `Cleaned ${result.count} seen non-deadline notifications`,
        data: { count: result.count },
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Read cleanup skipped.",
          data: { count: 0 },
        };
      }

      console.error("Error cleaning read non-deadline notifications:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to cleanup seen notifications",
      };
    }
  }

  /**
   * Creates a new notification for a user
   *
   * @param input - Notification creation parameters
   * @returns Promise with success status and notification data
   *
   * Steps:
   * 1. Calculate expiration time if provided (default: 30 days)
   * 2. Insert notification into database
   * 3. Return created notification with metadata
   *
   * Example:
   * ```typescript
   * await NotificationService.create({
   *   userId: "user123",
   *   type: "SUCCESS",
   *   title: "Contract Uploaded",
   *   message: "Your contract has been uploaded successfully"
   * });
   * ```
   */
  static async create(
    input: CreateNotificationInput,
  ): Promise<NotificationResponse> {
    try {
      // Calculate expiration time: default to 30 days if not specified
      const expiresAt = input.expiresIn
        ? new Date(Date.now() + input.expiresIn)
        : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

      // Create notification in database
      const notification = await prisma.notification.create({
        data: {
          userId: input.userId,
          type: input.type,
          title: input.title,
          message: input.message,
          contractId: input.contractId ?? undefined,
          actionType: input.actionType ?? undefined,
          actionData: input.actionData ?? undefined,
          icon: input.icon ?? undefined,
          expiresAt,
        },
      });

      return {
        success: true,
        data: notification,
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification skipped: table not available yet.",
        };
      }

      console.error("Error creating notification:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to create notification",
      };
    }
  }

  /**
   * Retrieves all unread notifications for a user
   *
   * @param userId - The user's ID
   * @param limit - Maximum number of notifications to return (default: 10)
   * @returns Promise with array of notifications sorted by creation date (newest first)
   *
   * Steps:
   * 1. Query database for unread notifications
   * 2. Filter out expired notifications (expiresAt < now)
   * 3. Sort by creation date (descending)
   * 4. Limit results to specified count
   *
   * Example:
   * ```typescript
   * const notifications = await NotificationService.getUnread("user123", 15);
   * ```
   */
  static async getUnread(
    userId: string,
    limit: number = 10,
  ): Promise<NotificationResponse> {
    try {
      // Enforce retention policy on each read path.
      await this.cleanupReadNonDeadline(userId);

      const notifications = await prisma.notification.findMany({
        where: {
          userId,
          read: false,
          OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }],
        },
        include: {
          contract: {
            select: {
              id: true,
              title: true,
              fileName: true,
              endDate: true,
            },
          },
        },
        orderBy: { createdAt: "desc" },
        take: limit,
      });

      return {
        success: true,
        data: notifications,
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          data: [],
        };
      }

      console.error("Error fetching unread notifications:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to fetch notifications",
      };
    }
  }

  /**
   * Retrieves all notifications for a user (read and unread)
   *
   * @param userId - The user's ID
   * @param limit - Maximum number of notifications to return (default: 50)
   * @returns Promise with array of all notifications sorted by creation date
   *
   * Used for displaying complete notification history/log
   */
  static async getAll(
    userId: string,
    limit: number = 50,
  ): Promise<NotificationResponse> {
    try {
      // Enforce retention policy on each read path.
      await this.cleanupReadNonDeadline(userId);

      const notifications = await prisma.notification.findMany({
        where: {
          userId,
          OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }],
        },
        include: {
          contract: {
            select: {
              id: true,
              title: true,
              fileName: true,
              endDate: true,
            },
          },
        },
        orderBy: { createdAt: "desc" },
        take: limit,
      });

      return {
        success: true,
        data: notifications,
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          data: [],
        };
      }

      console.error("Error fetching all notifications:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to fetch notifications",
      };
    }
  }

  /**
   * Marks a notification as read
   *
   * @param notificationId - The ID of the notification to mark as read
   * @returns Promise with success status
   *
   * Steps:
   * 1. Update notification read flag to true
   * 2. Return updated notification
   */
  static async markAsRead(
    notificationId: string,
  ): Promise<NotificationResponse> {
    try {
      const notification = await prisma.notification.update({
        where: { id: notificationId },
        data: { read: true },
      });

      if (notification.type !== "DEADLINE") {
        await prisma.notification.delete({
          where: { id: notificationId },
        });

        return {
          success: true,
          message: "Notification marked as read and auto-deleted",
          data: {
            id: notificationId,
            deleted: true,
          },
        };
      }

      return {
        success: true,
        data: notification,
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Mark-as-read skipped.",
        };
      }

      console.error("Error marking notification as read:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to mark notification as read",
      };
    }
  }

  /**
   * Marks all unread notifications as read for a user
   *
   * @param userId - The user's ID
   * @returns Promise with count of updated notifications
   */
  static async markAllAsRead(userId: string): Promise<NotificationResponse> {
    try {
      const result = await prisma.notification.updateMany({
        where: {
          userId,
          read: false,
        },
        data: { read: true },
      });

      const cleanup = await this.cleanupReadNonDeadline(userId);
      const cleanupCount = cleanup.success ? cleanup.data?.count || 0 : 0;

      return {
        success: true,
        message: `Marked ${result.count} notifications as read and auto-deleted ${cleanupCount} non-deadline items`,
        data: { count: result.count, deletedCount: cleanupCount },
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Mark-all-as-read skipped.",
          data: { count: 0 },
        };
      }

      console.error("Error marking all notifications as read:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to mark notifications as read",
      };
    }
  }

  /**
   * Deletes a notification
   *
   * @param notificationId - The ID of the notification to delete
   * @returns Promise with success status
   */
  static async delete(notificationId: string): Promise<NotificationResponse> {
    try {
      await prisma.notification.delete({
        where: { id: notificationId },
      });

      return {
        success: true,
        message: "Notification deleted successfully",
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Delete skipped.",
        };
      }

      console.error("Error deleting notification:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to delete notification",
      };
    }
  }

  /**
   * Cleans up expired notifications from the database
   *
   * Called periodically to remove old notifications
   * Only deletes notifications where expiresAt < current time
   *
   * @returns Promise with count of deleted notifications
   *
   * Example: Run daily via cron job or scheduled background task
   */
  static async cleanupExpired(): Promise<NotificationResponse> {
    try {
      const result = await prisma.notification.deleteMany({
        where: {
          expiresAt: {
            lt: new Date(),
          },
        },
      });

      return {
        success: true,
        message: `Cleaned up ${result.count} expired notifications`,
        data: { count: result.count },
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Cleanup skipped.",
          data: { count: 0 },
        };
      }

      console.error("Error cleaning up expired notifications:", error);
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to cleanup notifications",
      };
    }
  }

  /**
   * Gets unread notification count for a user
   *
   * Used for badge display on notification icon
   *
   * @param userId - The user's ID
   * @returns Promise with unread count
   *
   * Example:
   * ```typescript
   * const count = await NotificationService.getUnreadCount("user123");
   * // Display badge with count on notification icon
   * ```
   */
  static async getUnreadCount(userId: string): Promise<NotificationResponse> {
    try {
      // Enforce retention policy on count path as well.
      await this.cleanupReadNonDeadline(userId);

      const count = await prisma.notification.count({
        where: {
          userId,
          read: false,
          OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }],
        },
      });

      return {
        success: true,
        data: { count },
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          data: { count: 0 },
        };
      }

      console.error("Error getting unread count:", error);
      return {
        success: false,
        error:
          error instanceof Error ? error.message : "Failed to get unread count",
      };
    }
  }

  /**
   * Checks for upcoming contract renewals/expirations and creates notifications
   *
   * Scans all contracts for a user and creates DEADLINE notifications for:
   * - 30 days before expiration
   * - 14 days before expiration
   * - 7 days before expiration
   *
   * @param userId - The user's ID
   * @returns Promise with count of created notifications
   *
   * Steps:
   * 1. Query all COMPLETED contracts with endDate for the user
   * 2. Calculate days until expiration
   * 3. Create notification if contract expiring in 30, 14, or 7 days
   * 4. Check for existing notification to avoid duplicates
   * 5. Return summary of created notifications
   *
   * Example: Run daily via cron job
   * ```typescript
   * await NotificationService.checkUpcomingDeadlines("user123");
   * ```
   */
  static async checkUpcomingDeadlines(
    userId: string,
  ): Promise<NotificationResponse> {
    try {
      const today = new Date();
      today.setHours(0, 0, 0, 0);

      const user = await prisma.user.findUnique({
        where: { id: userId },
        select: {
          email: true,
          firstName: true,
          lastName: true,
        },
      });

      // Query all contracts with endDate for this user
      const contracts = await prisma.contract.findMany({
        where: {
          userId,
          status: "COMPLETED",
          endDate: {
            not: null,
          },
        },
        select: {
          id: true,
          title: true,
          endDate: true,
          provider: true,
        },
      });

      const createdNotifications: string[] = [];

      // Process each contract
      for (const contract of contracts) {
        if (!contract.endDate) continue;

        // Calculate days until expiration
        const contractEnd = new Date(contract.endDate);
        contractEnd.setHours(0, 0, 0, 0);
        const daysUntilExpiration = Math.ceil(
          (contractEnd.getTime() - today.getTime()) / (1000 * 60 * 60 * 24),
        );

        // Define deadline thresholds and notification levels
        let shouldNotify = false;
        let level = "";

        if (daysUntilExpiration === 7) {
          shouldNotify = true;
          level = "URGENT";
        } else if (daysUntilExpiration === 14) {
          shouldNotify = true;
          level = "WARNING";
        } else if (daysUntilExpiration === 30) {
          shouldNotify = true;
          level = "NOTICE";
        }

        if (shouldNotify) {
          // Check if notification already exists for this deadline
          const existingNotification = await prisma.notification.findFirst({
            where: {
              userId,
              contractId: contract.id,
              actionType: `RENEWAL_${level}`,
              createdAt: {
                gte: new Date(today.getTime() - 24 * 60 * 60 * 1000), // Within last 24 hours
              },
            },
          });

          // Only create if not already notified today
          if (!existingNotification) {
            const notificationTitle =
              level === "NOTICE"
                ? "Contract renewal reminder (30 days)"
                : level === "WARNING"
                  ? "Contract renewal window (14 days)"
                  : "Contract deadline in 7 days";

            const providerLabel = contract.provider || "your provider";
            const contractLabel = contract.title || "your contract";

            const notificationMessage =
              level === "NOTICE"
                ? `${contractLabel} from ${providerLabel} will expire on ${contractEnd.toLocaleDateString()}. Plan your renewal.`
                : level === "WARNING"
                  ? `${contractLabel} from ${providerLabel} expires in 14 days. Please review renewal steps.`
                  : `${contractLabel} from ${providerLabel} expires in 7 days. Action is required.`;

            const result = await this.create({
              userId,
              type: "DEADLINE",
              title: notificationTitle,
              message: notificationMessage,
              contractId: contract.id,
              actionType: `RENEWAL_${level}`,
              icon: level === "URGENT" ? "AlertCircle" : "AlertTriangle",
              expiresIn: 24 * 60 * 60 * 1000, // 24 hours
              actionData: {
                level,
                daysUntilExpiration,
                expirationDate: contractEnd.toISOString(),
                contractTitle: contract.title,
                contractProvider: contract.provider,
              },
            });

            if (result.success) {
              createdNotifications.push(contract.id);

              if (user?.email) {
                try {
                  await EmailService.sendContractDeadlineReminderEmail({
                    to: user.email,
                    userDisplayName:
                      `${user.firstName ?? ""} ${user.lastName ?? ""}`.trim() ||
                      null,
                    contractId: contract.id,
                    contractTitle: contract.title,
                    contractProvider: contract.provider,
                    contractEndDate: contractEnd,
                    daysUntilExpiration,
                  });
                } catch (emailError) {
                  console.warn(
                    "Deadline email failed:",
                    emailError instanceof Error
                      ? emailError.message
                      : emailError,
                  );
                }
              }
            }
          }
        }
      }

      return {
        success: true,
        message: `Created ${createdNotifications.length} deadline notifications`,
        data: {
          count: createdNotifications.length,
          contractIds: createdNotifications,
        },
      };
    } catch (error) {
      if (isNotificationTableMissingError(error)) {
        warnMissingNotificationTableOnce();
        return {
          success: true,
          message: "Notification table missing. Deadline scan skipped.",
          data: { count: 0, contractIds: [] },
        };
      }

      console.error("Error checking upcoming deadlines:", error);
      return {
        success: false,
        error:
          error instanceof Error ? error.message : "Failed to check deadlines",
      };
    }
  }
}
