import {
  Injectable,
  HttpService,
  ServiceUnavailableException,
} from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { CourseModel } from '../course/course.entity';
import * as fs from 'fs';
import * as path from 'path';
import * as checkDiskSpace from 'check-disk-space';
import { Connection } from 'typeorm';
import { RedisService } from 'nestjs-redis';
import * as Redlock from 'redlock';
import { ERROR_MESSAGES } from '@koh/common';

/**
 * Manage resources
 */
@Injectable()
export class ResourcesService {
  constructor(
    private connection: Connection,
    private httpService: HttpService,
    private readonly redisService: RedisService,
  ) {}

  /**
   * Refetches course calendar for all active courses. Any calendar files for
   * disabled courses are deleted from disk.
   */
  @Cron(CronExpression.EVERY_DAY_AT_1AM)
  public async refetchAllCalendarsJob(): Promise<void> {
    const resource = 'locks:icalcron';
    const ttl = 60000;

    const redisDB = await this.redisService.getClient('db');

    const redlock = new Redlock([redisDB]);

    redlock.on('clientError', function (err) {
      console.error('A redis error has occurred:', err);
    });

    try {
      await redlock.lock(resource, ttl).then(async (lock) => {
        console.log('updating course icals');
        await this.refetchAllCalendars();

        return lock.unlock().catch(function (err) {
          console.error('Error unlocking Redlock:', err);
        });
      });
      console.log('Successfully updated course calendars');
    } catch (error) {
      console.error('A problem locking Redlock has occurred:', error);
    }
  }

  private async refetchAllCalendars(): Promise<void> {
    // delete all cal files (will also get rid of files for disabled courses)
    const regex = /calendar-\d+$/;
    fs.readdirSync(process.env.UPLOAD_LOCATION)
      .filter((f) => regex.test(f))
      .map((f) => {
        try {
          fs.unlinkSync(path.join(process.env.UPLOAD_LOCATION, f));
          console.log('Unlinked calendar file', f);
        } catch (error) {
          console.error(`Error deleting calendar file ${f}:`, error);
        }
      });

    const courses = await CourseModel.find({ where: { enabled: true } });
    await Promise.all(courses.map((c) => this.refetchCalendar(c)));
  }

  /**
   * Fetch calendar for the given courseId, saving it to disk. Returns the string content
   * of the fetched calendar.
   */
  public async refetchCalendar(course: CourseModel): Promise<string> {
    const spaceLeft = await checkDiskSpace(path.parse(process.cwd()).root);
    if (spaceLeft.free < 1000000) {
      // less than 1000kb left (calendars range in size from 30-850kb)
      throw new ServiceUnavailableException(
        ERROR_MESSAGES.resourcesService.noDiskSpace,
      );
    }

    const request = await this.httpService.get(course.icalURL).toPromise();
    fs.writeFile(
      // not doing this synchronously so the request is faster
      path.join(process.env.UPLOAD_LOCATION, this.getCalFilename(course.id)),
      request.data,
      (err) => {
        if (err) {
          console.error(ERROR_MESSAGES.resourcesService.saveCalError, err);
        } else {
          console.log('Saved calendar for course ', course.id);
        }
      },
    );
    return request.data;
  }

  public getCalFilename(courseId: number) {
    return `calendar-${courseId}`;
  }
}