import 'reflect-metadata';
import {injectable, inject} from 'inversify';
import {ClientSession, Collection, MongoClient, ObjectId} from 'mongodb';
import {MongoDatabase} from '../MongoDatabase.js';
import {InternalServerError} from 'routing-controllers';
import {GLOBAL_TYPES} from '#root/types.js';
import {Invite} from '#root/modules/notifications/index.js';
import {InviteType} from '#root/shared/interfaces/models.js';

@injectable()
export class InviteRepository {
  private inviteCollection: Collection<Invite>;

  constructor(@inject(GLOBAL_TYPES.Database) private db: MongoDatabase) {}

  private async init() {
    this.inviteCollection = await this.db.getCollection<Invite>('invites');

    this.inviteCollection.createIndex({email: 1, inviteStatus: 1});
    this.inviteCollection.createIndex({
      courseId: 1,
      courseVersionId: 1,
      createdAt: -1,
    });
    this.inviteCollection.createIndex({courseVersionId: 1});
  }

  async getDBClient(): Promise<MongoClient> {
    const client = await this.db.getClient();
    if (!client) {
      throw new Error('MongoDB client is not initialized');
    }
    return client;
  }

  async create(invite: Invite, session?: ClientSession): Promise<string> {
    await this.init();
    try {
      if (invite.type === InviteType.BULK) {
        invite.usedCount = 0;
      }
      const result = await this.inviteCollection.insertOne(invite, {session});
      // const invitee = await this.inviteCollection.findOne({
      //   _id: result.insertedId,
      // });
      return result.insertedId.toString();
    } catch {
      throw new InternalServerError('Failed to create invite');
    }
  }

  async incrementUsedCount(
    inviteId: string,
    session?: ClientSession,
  ): Promise<void> {
    await this.init();
    // const result = await this.inviteCollection.updateOne(
    //   {_id: new ObjectId(inviteId)},{$inc:{usedCount:1}},{session})
  }

  async all() {
    return this.inviteCollection.find();
  }

  async findInviteById(
    id: string,
    session?: ClientSession,
  ): Promise<Invite | null> {
    await this.init(); // Ensure collection is initialized
    const invite = await this.inviteCollection.findOne(
      {_id: new ObjectId(id)},
      {session},
    );
    if (!invite) return null;

    return {
      ...invite,
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
      usedCount: invite.usedCount || 0,
    };
  }

  async findInvitesByIds(
    ids: string[],
    session?: ClientSession,
  ): Promise<Invite[]> {
    await this.init(); // Ensure collection is initialized

    if (!ids || ids.length === 0) {
      return [];
    }

    const objectIds = ids.map(id => new ObjectId(id));
    const invites = await this.inviteCollection
      .find({_id: {$in: objectIds}}, {session})
      .toArray();

    return invites.map(invite => ({
      ...invite,
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
    }));
  }

  async updateInvite(
    inviteId: string,
    inviteData: Partial<Invite>,
    session?: ClientSession,
  ): Promise<void> {
    await this.init();

    const result = await this.inviteCollection.updateOne(
      {_id: new ObjectId(inviteId)},
      {$set: inviteData},
      {session},
    );

    if (result.matchedCount === 0) {
      throw new Error(`Failed to update invite with ID: ${inviteId}`);
    }
  }

  async findInvitesByEmail(
    email: string,
    session?: ClientSession,
  ): Promise<Invite[]> {
    await this.init(); // Ensure collection is initialized

    const invites = await this.inviteCollection
      .find({email: email.toLowerCase().trim()}, {session})
      .toArray();

    return invites.map(invite => ({
      ...invite,
      _id: invite._id.toString(),
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
    }));
  }

  async updateUserToNotNewUser(
    email: string,
    session?: ClientSession,
  ): Promise<void> {
    await this.init();

    await this.inviteCollection.updateMany(
      {email: email.toLowerCase().trim(), isNewUser: true},
      {$set: {isNewUser: false}},
      {session},
    );
  }

  async findPendingInvitesByEmail(
    email: string,
    session?: ClientSession,
  ): Promise<Invite[]> {
    await this.init(); // Ensure collection is initialized

    const invites = await this.inviteCollection
      .find({email: email.toLowerCase().trim(), inviteStatus: 'PENDING'}, {session})
      .toArray();

    return invites.map(invite => ({
      ...invite,
      _id: invite._id.toString(),
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
    }));
  }

  async findPendingInviteByEmailAndCourse(
    email: string,
    courseId: string,
    courseVersionId: string,
    cohortId?:string,
    session?: ClientSession,
  ): Promise<Invite | null> {
    await this.init();

    const invite = await this.inviteCollection.findOne(
      {
        email: email.toLowerCase(),
        courseId: new ObjectId(courseId),
        courseVersionId: new ObjectId(courseVersionId),
        ...(cohortId ? { cohortId: new ObjectId(cohortId) } : {cohortId: null }),
        inviteStatus: 'PENDING',
      },
      {session},
    );

    if (!invite) return null;

    return {
      ...invite,
      _id: invite._id.toString(),
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
    };
  }

  async findInvitesByCourse(
    courseId: string,
    courseVersionId: string,
    inviteStatus: string,
    currentPage: number,
    limit: number,
    search: string,
    sort: string,
    startDate?: string,
    endDate?: string,
    session?: ClientSession,
  ): Promise<{invites: Invite[]; totalDocuments: number; totalPages: number}> {
    await this.init();

    const courseIdObj = ObjectId.isValid(courseId)
      ? new ObjectId(courseId)
      : null;
    const courseVersionIdObj = ObjectId.isValid(courseVersionId)
      ? new ObjectId(courseVersionId)
      : null;

    const filter: any = {
      courseId: {$in: [courseId, ...(courseIdObj ? [courseIdObj] : [])]},
      courseVersionId: {
        $in: [
          courseVersionId,
          ...(courseVersionIdObj ? [courseVersionIdObj] : []),
        ],
      },
    };
    // const filter: any = {courseId, courseVersionId};

    if (inviteStatus) {
      filter.inviteStatus = inviteStatus;
    }

    if (search) {
      filter.email = {$regex: search, $options: 'i'};
    }

    if (startDate || endDate) {
      filter.createdAt = {};
      if (startDate) {
        const startDateTime = new Date(startDate);
        startDateTime.setUTCHours(0, 0, 0, 0);
        filter.createdAt.$gte = startDateTime;
      }
      if (endDate) {
        const endDateTime = new Date(endDate);
        endDateTime.setUTCHours(23, 59, 59, 999);
        filter.createdAt.$lte = endDateTime;
      }
    }

    const sortStage: Record<string, 1 | -1> = (() => {
      switch (sort) {
        case 'accept_date_desc':
          return {acceptedAt: -1};
        case 'accept_date_asc':
          return {acceptedAt: 1};
        default:
          return {createdAt: -1};
      }
    })();

    const skip = (currentPage - 1) * limit;

    const [invites, totalDocuments] = await Promise.all([
      this.inviteCollection
        .find(filter, {session})
        .sort(sortStage)
        .skip(skip)
        .limit(limit)
        .toArray(),
      this.inviteCollection.countDocuments(filter),
    ]);

    const totalPages = Math.ceil(totalDocuments / limit);

    const normalizedInvites = invites.map(invite => ({
      ...invite,
      courseId: invite.courseId?.toString(),
      courseVersionId: invite.courseVersionId?.toString(),
    }));

    return {invites: normalizedInvites, totalDocuments, totalPages};
  }

  async deleteInviteByVersionId(versionId: string, session?: ClientSession) {
    await this.init();
    await this.inviteCollection.deleteMany(
      {courseVersionId: new ObjectId(versionId)},
      {session},
    );
  }

  // async cancelPendingInvitesByCourseId(
  //   courseId: string,
  //   session?: ClientSession,
  // ): Promise<void> {
  //   await this.inviteCollection.updateMany(
  //     {
  //       courseId: new ObjectId(courseId),
  //       inviteStatus: {$in: ['PENDING', 'EMAIL_FAILED']},
  //     },
  //     {$set: {inviteStatus: 'CANCELLED'}},
  //     {session},
  //   );
  // }

  async cancelPendingInvites(
    filter: {courseId?: string; courseVersionId?: string},
    session?: ClientSession,
  ): Promise<void> {
    const query: Record<string, any> = {
      inviteStatus: {$in: ['PENDING', 'EMAIL_FAILED']},
    };

    if (filter.courseId) query.courseId = new ObjectId(filter.courseId);
    if (filter.courseVersionId)
      query.courseVersionId = new ObjectId(filter.courseVersionId);

    await this.inviteCollection.updateMany(
      query,
      {$set: {inviteStatus: 'CANCELLED'}},
      {session},
    );
  }
}
