import { Types } from 'mongoose';
import ScanModel from '../../models/Scan';
import QRCodeModel from '../../models/QrCode';
import { CreateScanData } from '../../types/scan.types';
import UserModel from '../../models/User';
import SubscriptionModel from '../../models/Subscription';

/**
 * Scan Repository
 * Handles all database operations for scans
 */
export class ScanRepository {
  /**
   * Create a new scan record
   */
  static async createScan(scanData: CreateScanData & { isPublic?: boolean }) {
    const raw: Record<string, unknown> = {
      qrcode_id: scanData.qrcode_id,
      scan_location: scanData.scan_location,
      notes: scanData.notes,
      is_anonymous: scanData.is_anonymous,
      device_info: scanData.device_info,
      device_type: scanData.device_type,
      isPublic: (scanData as { isPublic?: boolean }).isPublic ?? false,
    };

    const cid = scanData.coordinate_id;
    if (cid != null && String(cid).length > 0 && Types.ObjectId.isValid(String(cid))) {
      raw.coordinate_id =
        cid instanceof Types.ObjectId ? cid : new Types.ObjectId(String(cid));
    }

    const sid = scanData.scanned_by;
    if (sid != null && String(sid).length > 0 && Types.ObjectId.isValid(String(sid))) {
      raw.scanned_by =
        sid instanceof Types.ObjectId ? sid : new Types.ObjectId(String(sid));
    }

    const scan = new ScanModel(raw);
    return await scan.save();
  }

  /**
   * Find QR code by ID
   */
  static async findQRCodeById(qrcodeId: string) {
    if (!Types.ObjectId.isValid(qrcodeId)) {
      return null;
    }
    return (await QRCodeModel.findById(qrcodeId).lean()) as any;
  }

  /**
   * Find QR code by ID with specific fields
   */
  static async findQRCodeByIdWithFields(qrcodeId: string, fields: string) {
    if (!Types.ObjectId.isValid(qrcodeId)) {
      return null;
    }
    return await QRCodeModel.findById(qrcodeId).select(fields).lean();
  }

  /**
   * Count scans with query
   */
  static async countScans(query: any): Promise<number> {
    return await ScanModel.countDocuments(query);
  }

  /**
   * Find scans with pagination and population
   */
  static async findScansWithPagination(
    query: any,
    skip: number,
    limit: number,
    search?: string
  ) {
    return await ScanModel.find(query)
      .populate([
        {
          path: 'qrcode_id',
          select: 'title type status access_type',
          match:
            search && search.trim() ? { title: new RegExp(search.trim(), 'i') } : {},
        },
        {
          path: 'coordinate_id',
          select: 'latitude longitude',
        },
        {
          path: 'scanned_by',
          select: 'first_name last_name email',
          match:
            search && search.trim()
              ? {
                  $or: [
                    { first_name: new RegExp(search.trim(), 'i') },
                    { last_name: new RegExp(search.trim(), 'i') },
                    { email: new RegExp(search.trim(), 'i') },
                  ],
                }
              : {},
        },
      ])
      .sort({ created_at: -1 })
      .skip(skip)
      .limit(limit)
      .lean();
  }

  /**
   * Find scan by ID
   * Uses aggregation to handle scanned_by from both Users and GuestUser models
   */
  static async findScanById(scanId: string, userId?: string) {
    if (!Types.ObjectId.isValid(scanId)) {
      return null;
    }

    const matchStage: any = { _id: new Types.ObjectId(scanId) };

    const pipeline = [
      { $match: matchStage },

      // Normalize scanned_by to ObjectId for $lookup (string vs ObjectId mismatch otherwise yields empty joins)
      {
        $addFields: {
          _scannerLookupId: {
            $convert: {
              input: '$scanned_by',
              to: 'objectId',
              onError: null,
              onNull: null,
            },
          },
        },
      },

      // Lookup coordinate_id
      {
        $lookup: {
          from: 'locations',
          localField: 'coordinate_id',
          foreignField: '_id',
          as: 'coordinate_id',
        },
      },
      { $unwind: { path: '$coordinate_id', preserveNullAndEmptyArrays: true } },

      // Lookup scanned_by in users collection
      {
        $lookup: {
          from: 'users',
          localField: '_scannerLookupId',
          foreignField: '_id',
          as: 'scanned_by_user',
        },
      },

      // Lookup scanned_by in guest_users collection
      {
        $lookup: {
          from: 'guest_users',
          localField: '_scannerLookupId',
          foreignField: '_id',
          as: 'scanned_by_guest',
        },
      },

      // Merge scanned_by from both collections
      {
        $addFields: {
          scanned_by: {
            $cond: {
              if: { $gt: [{ $size: '$scanned_by_user' }, 0] },
              then: { $arrayElemAt: ['$scanned_by_user', 0] },
              else: {
                $cond: {
                  if: { $gt: [{ $size: '$scanned_by_guest' }, 0] },
                  then: { $arrayElemAt: ['$scanned_by_guest', 0] },
                  else: null,
                },
              },
            },
          },
        },
      },

      // Lookup qrcode_id
      {
        $lookup: {
          from: 'qrcodes',
          localField: 'qrcode_id',
          foreignField: '_id',
          as: 'qrcode_id',
        },
      },
      { $unwind: { path: '$qrcode_id', preserveNullAndEmptyArrays: true } },

      // Lookup created_by for qrcode
      {
        $lookup: {
          from: 'users',
          localField: 'qrcode_id.created_by',
          foreignField: '_id',
          as: 'qrcode_id.created_by',
        },
      },
      {
        $addFields: {
          'qrcode_id.created_by': { $arrayElemAt: ['$qrcode_id.created_by', 0] },
        },
      },

      // Lookup images for qrcode
      {
        $lookup: {
          from: 'media',
          localField: 'qrcode_id.images',
          foreignField: '_id',
          as: 'qrcode_id.images',
        },
      },

      // Lookup location for qrcode
      {
        $lookup: {
          from: 'locations',
          localField: 'qrcode_id.location',
          foreignField: '_id',
          as: 'qrcode_id.location',
        },
      },
      {
        $addFields: {
          'qrcode_id.location': { $arrayElemAt: ['$qrcode_id.location', 0] },
        },
      },

      // Lookup memory_tag location
      {
        $lookup: {
          from: 'locations',
          localField: 'qrcode_id.memory_tag.location',
          foreignField: '_id',
          as: 'memory_tag_location',
        },
      },

      // Lookup memory_tag images
      {
        $lookup: {
          from: 'media',
          localField: 'qrcode_id.memory_tag.images',
          foreignField: '_id',
          as: 'memory_tag_images',
        },
      },

      // Add memory_tag with populated images and location
      {
        $addFields: {
          'qrcode_id.memory_tag': {
            $cond: {
              if: { $ifNull: ['$qrcode_id.memory_tag', false] },
              then: {
                $mergeObjects: [
                  '$qrcode_id.memory_tag',
                  {
                    location: { $arrayElemAt: ['$memory_tag_location', 0] },
                    images: '$memory_tag_images',
                  },
                ],
              },
              else: null,
            },
          },
        },
      },

      // For MEMORY_TAG type, set qrcode_id.images to memory_tag_images
      {
        $addFields: {
          'qrcode_id.images': {
            $cond: {
              if: { $eq: ['$qrcode_id.type', 'MEMORY_TAG'] },
              then: '$memory_tag_images',
              else: '$qrcode_id.images',
            },
          },
        },
      },

      // Remove temporary fields
      {
        $project: {
          scanned_by_user: 0,
          scanned_by_guest: 0,
          memory_tag_location: 0,
          memory_tag_images: 0,
          _scannerLookupId: 0,
        },
      },
    ];

    const result = await ScanModel.aggregate(pipeline);

    return result[0] || null;
  }

  /**
   * Update scan by ID
   */
  static async updateScan(scanId: string, userId: string, data: any) {
    if (!Types.ObjectId.isValid(scanId) || !Types.ObjectId.isValid(userId)) {
      return null;
    }

    return await ScanModel.findOneAndUpdate(
      {
        _id: new Types.ObjectId(scanId),
        scanned_by: new Types.ObjectId(userId),
      },
      data,
      { new: true }
    )
      .populate([
        {
          path: 'qrcode_id',
          select: 'title type status access_type',
        },
        {
          path: 'coordinate_id',
          select: 'latitude longitude',
        },
        {
          path: 'scanned_by',
          select: 'first_name last_name email',
        },
      ])
      .lean();
  }

  /**
   * Delete scan by ID
   */
  static async deleteScan(scanId: string, userId: string) {
    if (!Types.ObjectId.isValid(scanId) || !Types.ObjectId.isValid(userId)) {
      return null;
    }

    return await ScanModel.findOneAndDelete({
      _id: new Types.ObjectId(scanId),
      scanned_by: new Types.ObjectId(userId),
    });
  }

  /**
   * Get scan analytics with aggregation
   */
  static async getScanAnalytics(matchQuery: any, groupStage: any) {
    const pipeline = [
      { $match: matchQuery },
      {
        $group: {
          _id: groupStage,
          totalScans: { $sum: 1 },
        },
      },
      { $sort: { _id: 1 as 1 } },
    ];

    return await ScanModel.aggregate(pipeline as any);
  }

  /**
   * Find scans by user and QR code
   */
  static async findScansByUserAndQRCode(userId: string, qrCodeId: string) {
    return await ScanModel.find({
      scanned_by: new Types.ObjectId(userId),
      qrcode_id: new Types.ObjectId(qrCodeId),
    }).lean();
  }

  /**
   * Count scans performed by user on memory tag QR codes in the current month
   */
  static async countUserMemoryTagScansInMonth(userId: string, startDate: Date) {
    // Scan schema uses Mongoose `timestamps` → `createdAt` (not `created_at`)
    const start = new Date(startDate);
    const idStr = String(userId);
    if (!Types.ObjectId.isValid(idStr)) {
      return 0;
    }
    const scannerId = new Types.ObjectId(idStr);
    // Aggregation joins scans → qrcodes to count MEMORY_TAG scans in the current month
    const pipeline = [
      {
        $match: {
          scanned_by: scannerId,
          $or: [
            { createdAt: { $gte: start } },
            { created_at: { $gte: start } },
          ],
        },
      },
      {
        $lookup: {
          from: 'qrcodes',
          localField: 'qrcode_id',
          foreignField: '_id',
          as: 'qrcode',
        },
      },
      {
        $unwind: '$qrcode',
      },
      {
        $match: {
          'qrcode.type': 'MEMORY_TAG',
        },
      },
      {
        $count: 'totalMemoryTagScans',
      },
    ];

    const result = await ScanModel.aggregate(pipeline);
    return result[0]?.totalMemoryTagScans || 0;
  }

  /**
   * QR CODE OWNER
   * **/
  static async qrCodeOwner(userId: string) {
    return SubscriptionModel.findOne({ user_id: new Types.ObjectId(userId!) }).lean();
  }
}
