import { CampaignStatus, Prisma } from "@prisma/client";
import { TRPCError } from "@trpc/server";
import { EmailRenderer } from "@usesend/email-editor/src/renderer";
import { z } from "zod";
import { env } from "~/env";
import {
  teamProcedure,
  createTRPCRouter,
  campaignProcedure,
  publicProcedure,
} from "~/server/api/trpc";
import { logger } from "~/server/logger/log";
import { nanoid } from "~/server/nanoid";
import * as campaignService from "~/server/service/campaign-service";
import { validateDomainFromEmail } from "~/server/service/domain-service";
import {
  getDocumentUploadUrl,
  isStorageConfigured,
} from "~/server/service/storage-service";

const statuses = Object.values(CampaignStatus) as [CampaignStatus];

export const campaignRouter = createTRPCRouter({
  getCampaigns: teamProcedure
    .input(
      z.object({
        page: z.number().optional(),
        status: z.enum(statuses).optional().nullable(),
        search: z.string().optional().nullable(),
      }),
    )
    .query(async ({ ctx: { db, team }, input }) => {
      const page = input.page || 1;
      const limit = 30;
      const offset = (page - 1) * limit;

      const whereConditions: Prisma.CampaignFindManyArgs["where"] = {
        teamId: team.id,
      };

      if (input.status) {
        whereConditions.status = input.status;
      }

      if (input.search) {
        whereConditions.OR = [
          {
            name: {
              contains: input.search,
              mode: "insensitive",
            },
          },
          {
            subject: {
              contains: input.search,
              mode: "insensitive",
            },
          },
        ];
      }

      const countP = db.campaign.count({ where: whereConditions });

      const campaignsP = db.campaign.findMany({
        where: whereConditions,
        select: {
          id: true,
          name: true,
          from: true,
          subject: true,
          createdAt: true,
          updatedAt: true,
          status: true,
          scheduledAt: true,
          total: true,
          sent: true,
          delivered: true,
          unsubscribed: true,
        },
        orderBy: {
          createdAt: "desc",
        },
        skip: offset,
        take: limit,
      });

      const [campaigns, count] = await Promise.all([campaignsP, countP]);

      return { campaigns, totalPage: Math.ceil(count / limit) };
    }),

  createCampaign: teamProcedure
    .input(
      z.object({
        name: z.string(),
        from: z.string(),
        subject: z.string(),
      }),
    )
    .mutation(async ({ ctx: { db, team }, input }) => {
      const domain = await validateDomainFromEmail(input.from, team.id);

      const campaign = await db.campaign.create({
        data: {
          ...input,
          teamId: team.id,
          domainId: domain.id,
        },
      });

      return campaign;
    }),

  updateCampaign: campaignProcedure
    .input(
      z.object({
        name: z.string().optional(),
        from: z.string().optional(),
        subject: z.string().optional(),
        previewText: z.string().optional(),
        content: z.string().optional(),
        html: z.string().optional(),
        contactBookId: z.string().optional(),
        replyTo: z.string().array().optional(),
      }),
    )
    .mutation(async ({ ctx: { db, team, campaign: campaignOld }, input }) => {
      const { html: htmlInput, campaignId, ...data } = input;
      if (data.contactBookId) {
        const contactBook = await db.contactBook.findUnique({
          where: { id: data.contactBookId, teamId: team.id },
        });

        if (!contactBook) {
          throw new TRPCError({
            code: "BAD_REQUEST",
            message: "Contact book not found",
          });
        }
      }
      let domainId = campaignOld.domainId;
      if (data.from) {
        const domain = await validateDomainFromEmail(data.from, team.id);
        domainId = domain.id;
      }

      let htmlToSave: string | undefined;

      if (data.content) {
        const jsonContent = data.content ? JSON.parse(data.content) : null;

        const renderer = new EmailRenderer(jsonContent);
        htmlToSave = await renderer.render();
      } else if (typeof htmlInput === "string") {
        htmlToSave = htmlInput;
      }

      const campaignUpdateData: Prisma.CampaignUpdateInput = {
        ...data,
        domainId,
      };

      if (htmlToSave !== undefined) {
        campaignUpdateData.html = htmlToSave;
      }

      const campaign = await db.campaign.update({
        where: { id: campaignId },
        data: campaignUpdateData,
      });
      return campaign;
    }),

  deleteCampaign: campaignProcedure.mutation(async ({ ctx: { team }, input }) => {
    return await campaignService.deleteCampaign(input.campaignId, team.id);
  }),

  getCampaign: campaignProcedure.query(async ({ ctx: { db, team }, input }) => {
    const campaign = await db.campaign.findUnique({
      where: { id: input.campaignId, teamId: team.id },
    });

    if (!campaign) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: "Campaign not found",
      });
    }

    const imageUploadSupported = isStorageConfigured();

    if (campaign?.contactBookId) {
      const contactBook = await db.contactBook.findUnique({
        where: { id: campaign.contactBookId, teamId: team.id },
      });
      return { ...campaign, contactBook, imageUploadSupported };
    }
    return {
      ...campaign,
      contactBook: null,
      imageUploadSupported,
    };
  }),

  latestEmails: campaignProcedure.query(
    async ({ ctx: { db, team, campaign } }) => {
      const emails = await db.email.findMany({
        where: {
          teamId: team.id,
          campaignId: campaign.id,
        },
        orderBy: [{ updatedAt: "desc" }, { createdAt: "desc" }],
        take: 10,
        select: {
          id: true,
          subject: true,
          to: true,
          latestStatus: true,
          createdAt: true,
          updatedAt: true,
          scheduledAt: true,
        },
      });

      return emails;
    },
  ),

  reSubscribeContact: publicProcedure
    .input(
      z.object({
        id: z.string(),
        hash: z.string(),
      }),
    )
    .mutation(async ({ input }) => {
      await campaignService.subscribeContact(input.id, input.hash);
    }),

  duplicateCampaign: campaignProcedure.mutation(
    async ({ ctx: { db, team, campaign } }) => {
      const newCampaign = await db.campaign.create({
        data: {
          name: `${campaign.name} (Copy)`,
          from: campaign.from,
          replyTo: campaign.replyTo,
          cc: campaign.cc,
          bcc: campaign.bcc,
          subject: campaign.subject,
          previewText: campaign.previewText,
          content: campaign.content,
          html: campaign.html,
          teamId: team.id,
          domainId: campaign.domainId,
          contactBookId: campaign.contactBookId,
        },
      });

      return newCampaign;
    },
  ),

  scheduleCampaign: campaignProcedure
    .input(
      z.object({
        campaignId: z.string(),
        scheduledAt: z.union([z.string().datetime(), z.date()]).optional(),
        batchSize: z.number().min(1).max(100_000).optional(),
      }),
    )
    .mutation(async ({ ctx: { team }, input }) => {
      await campaignService.scheduleCampaign({
        campaignId: input.campaignId,
        teamId: team.id,
        scheduledAt: input.scheduledAt,
        batchSize: input.batchSize,
      });
      return { ok: true };
    }),

  pauseCampaign: campaignProcedure.mutation(async ({ ctx: { campaign } }) => {
    await campaignService.pauseCampaign({
      campaignId: campaign.id,
      teamId: campaign.teamId,
    });
    return { ok: true };
  }),

  resumeCampaign: campaignProcedure.mutation(async ({ ctx: { campaign } }) => {
    await campaignService.resumeCampaign({
      campaignId: campaign.id,
      teamId: campaign.teamId,
    });
    return { ok: true };
  }),

  generateImagePresignedUrl: campaignProcedure
    .input(
      z.object({
        name: z.string(),
        type: z.string(),
      }),
    )
    .mutation(async ({ ctx: { team }, input }) => {
      const extension = input.name.split(".").pop();
      const randomName = `${nanoid()}.${extension}`;

      const url = await getDocumentUploadUrl(
        `${team.id}/${randomName}`,
        input.type,
      );

      const imageUrl = `${env.S3_COMPATIBLE_PUBLIC_URL}/${team.id}/${randomName}`;

      return { uploadUrl: url, imageUrl };
    }),
});
