import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cron, CronExpression } from '@nestjs/schedule';
import { Model } from 'mongoose';
import { md5 } from '../shared/utils';
import { IMailchimpSubscription } from './interface/mailchimp-subscription';
import { INewsletterSubscription } from './interface/newsletter-subscription.interface';
import { NewsletterSubscription } from './newsletter-subscription.schema';
import mailchimp = require('@mailchimp/mailchimp_marketing');
import { UsersService } from '../users/services/users.service';

@Injectable()
export class NewsletterService {
  private readonly logger = new Logger(NewsletterService.name);
  private LIST_ID = process.env.MC_LIST_ID;
  constructor(
    @InjectModel(NewsletterSubscription.name) private newsletterSubscriptionModel: Model<INewsletterSubscription>,
    private readonly userService: UsersService
  ) {
    // Configure mailchimp client
    mailchimp.setConfig({
      apiKey: process.env.MC_API_KEY,
      server: process.env.MC_SERVER,
    });
  }

  @Cron(CronExpression.EVERY_DAY_AT_3AM)
  public async updateNewsletterSubscription(): Promise<void> {
    this.logger.debug('updateNewsletterSubscription');
    const { total_items } = await mailchimp.lists.getListMembersInfo(this.LIST_ID);
    const { members } = await mailchimp.lists.getListMembersInfo(this.LIST_ID, {
      fields: ['members.email_address,members.id,members.status'],
      count: total_items,
    });
    const memberToRemove = members.filter((user: IMailchimpSubscription) => user.status !== 'subscribed');

    memberToRemove.forEach(async (member: IMailchimpSubscription) => {
      const userSubscription = await this.findOne(member.email_address);
      if (userSubscription) {
        this.logger.log(`Remove subscription ${member.id}`);
        userSubscription.deleteOne();
      }
    });
  }

  public async setMedNumTag(email: string, status: boolean) {
    this.logger.debug(`newsletter MedNum tag: ${email} - ${status}`);
    return mailchimp.lists
      .updateListMemberTags(this.LIST_ID, md5(email), {
        tags: [{ name: 'MedNum', status: status ? 'active' : 'inactive' }],
      })
      .then(() => this.logger.log(`newsletter MedNum set: ${email} - ${status}`))
      .catch((e) => {
        // email may not be found in mailchimp if the user hadn't subscribed to the newsletter
        if (e.status === 404) {
          this.logger.debug(`newsletter MedNum tag: user not found: ${email} - ${status}`);
        } else {
          this.logger.error(`newsletter MedNum tag error: ${e.status} - ${e}`);
        }
      });
  }

  public async newsletterSubscribe(email: string): Promise<NewsletterSubscription> {
    this.logger.log(`newsletterSubscribe: ${email}`);
    email = email.toLocaleLowerCase();
    try {
      // Add or update list member (to be able to subscribe again a member who had already subscribed then unsubscribed)
      // (the second parameter is the MD5 hash of the lowercase email, we actually don't need to maintain a mapping in newsletterSubscription : cf. https://mailchimp.com/developer/marketing/docs/methods-parameters/#path-parameters )
      const member = await mailchimp.lists.setListMember(this.LIST_ID, md5(email), {
        email_address: email,
        status_if_new: 'subscribed', // cf. https://mailchimp.com/developer/marketing/api/list-members/add-or-update-list-member/
        status: 'subscribed',
      });

      // We may not be aware that the user has unsubscribed from the newsletter, so it is ok if it already exists in newsletterSubscription
      let newsletterSubscription = await this.findOne(email);
      if (!newsletterSubscription) {
        newsletterSubscription = await this.newsletterSubscriptionModel.create({
          email: email,
          mailchimpId: member.id,
        });
      }

      // Find user for this email to update his MedNum tag (user may not exist if it is a newsletter subscription from footer)
      const user = await this.userService.findOne(email);
      this.setMedNumTag(email, user?.job?.hasPersonalOffer);

      return newsletterSubscription;
    } catch (e) {
      if (e.status === 400) {
        if (e.response?.text?.includes('fake')) {
          this.logger.log(`newsletterSubscribe: Fake or invalid email: ${email}`);
          throw new HttpException('Fake or invalid email', HttpStatus.I_AM_A_TEAPOT);
        } else {
          // Example: email "has signed up to a lot of lists very recently; we're not allowing more signups for now"
          this.logger.error(`newsletterSubscribe ${email}: ${JSON.stringify(e)}`);
          throw new HttpException(JSON.parse(e.response?.text)?.detail, HttpStatus.BAD_REQUEST);
        }
      } else {
        this.logger.error(`newsletterSubscribe ${email}: ${JSON.stringify(e)}`);
        throw new HttpException('Subscribe error', HttpStatus.INTERNAL_SERVER_ERROR);
      }
    }
  }

  public async newsletterUnsubscribe(email: string): Promise<NewsletterSubscription> {
    this.logger.log(`newsletterUnsubscribe: ${email}`);
    email = email.toLocaleLowerCase();
    const emailMd5Hashed = md5(email);

    let newsletterSubscription = await this.findOne(email);
    if (newsletterSubscription) {
      newsletterSubscription = newsletterSubscription.deleteOne();
    }

    try {
      const response = await mailchimp.lists.getListMember(this.LIST_ID, emailMd5Hashed);
      if (response.status === 'unsubscribed') {
        throw new HttpException('Email not found', HttpStatus.NOT_FOUND);
      }
      await mailchimp.lists.setListMember(this.LIST_ID, emailMd5Hashed, {
        status: 'unsubscribed',
      });
    } catch (e) {
      if (e.status === 404) {
        throw new HttpException('Email not found', HttpStatus.NOT_FOUND);
      } else {
        this.logger.error(`newsletterUnsubscribe ${email}: ${JSON.stringify(e)}`);
        throw new HttpException('Unsubscribe error', HttpStatus.INTERNAL_SERVER_ERROR);
      }
    }

    return newsletterSubscription;
  }

  public async findNewsletterSubscription(email: string): Promise<object> {
    try {
      // The actual subscription info is in mailchimp, we actually don't need to maintain a mapping in newsletterSubscription : cf. https://mailchimp.com/developer/marketing/docs/methods-parameters/#path-parameters
      const mailchimpUser = await mailchimp.lists.getListMember(this.LIST_ID, md5(email));
      return mailchimpUser.status === 'unsubscribed' ? null : { email };
    } catch (e) {
      if (e.status === 404) {
        return null;
      } else {
        this.logger.error(`findNewsletterSubscription: ${e.status} - ${e}`);
        throw new HttpException('find Newsletter Subscription', HttpStatus.INTERNAL_SERVER_ERROR);
      }
    }
  }

  public async findOne(mail: string): Promise<INewsletterSubscription | undefined> {
    this.logger.debug('findOne');
    return this.newsletterSubscriptionModel.findOne({ email: mail }).exec();
  }

  public async findAll(): Promise<NewsletterSubscription[]> {
    this.logger.debug('findAll');
    return this.newsletterSubscriptionModel.find().exec();
  }
}