import { PrismaClient, Prisma } from '@prisma/client';
import type {
  ListEmailTemplatesDto,
  CreateEmailTemplateDto,
  UpdateEmailTemplateDto,
  RollbackToVersionDto,
  ValidateTemplateDto,
  SendTestEmailDto,
} from './email-templates.schemas';
import { logger } from '../../utils/logger';
import { emailService } from '../../services/email.service';

const prisma = new PrismaClient();

interface EmailTemplatesListResponse {
  templates: any[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
}

interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings?: string[];
  extractedVariables?: string[];
}

export class EmailTemplatesService {
  /**
   * List email templates with pagination, search, and filters
   */
  async list(params: ListEmailTemplatesDto): Promise<EmailTemplatesListResponse> {
    const { page, limit, search, category, isActive } = params;
    const skip = (page - 1) * limit;

    // Build where clause
    const where: Prisma.EmailTemplateWhereInput = {
      ...(search && {
        OR: [
          { key: { contains: search, mode: 'insensitive' as Prisma.QueryMode } },
          { name: { contains: search, mode: 'insensitive' as Prisma.QueryMode } },
          { description: { contains: search, mode: 'insensitive' as Prisma.QueryMode } },
        ],
      }),
      ...(category && { category }),
      ...(isActive !== undefined && { isActive }),
    };

    const [data, total] = await Promise.all([
      prisma.emailTemplate.findMany({
        where,
        skip,
        take: limit,
        orderBy: [{ isSystem: 'desc' }, { category: 'asc' }, { name: 'asc' }],
        include: {
          variables: {
            orderBy: { sortOrder: 'asc' },
          },
          _count: {
            select: {
              versions: true,
              testLogs: true,
            },
          },
          createdBy: {
            select: { id: true, name: true, email: true },
          },
          updatedBy: {
            select: { id: true, name: true, email: true },
          },
        },
      }),
      prisma.emailTemplate.count({ where }),
    ]);

    return {
      templates: data,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Get a single email template by ID
   */
  async getById(id: string) {
    const template = await prisma.emailTemplate.findUnique({
      where: { id },
      include: {
        variables: {
          orderBy: { sortOrder: 'asc' },
        },
        _count: {
          select: {
            versions: true,
            testLogs: true,
          },
        },
        createdBy: {
          select: { id: true, name: true, email: true },
        },
        updatedBy: {
          select: { id: true, name: true, email: true },
        },
      },
    });

    if (!template) {
      throw new Error('Template not found');
    }

    return template;
  }

  /**
   * Get template by key
   */
  async getByKey(key: string) {
    const template = await prisma.emailTemplate.findUnique({
      where: { key },
      include: {
        variables: {
          orderBy: { sortOrder: 'asc' },
        },
      },
    });

    return template;
  }

  /**
   * Create a new email template
   */
  async create(data: CreateEmailTemplateDto, userId: string) {
    // Check for duplicate key
    const existing = await prisma.emailTemplate.findUnique({
      where: { key: data.key },
    });

    if (existing) {
      throw new Error(`Template with key "${data.key}" already exists`);
    }

    // Validate template content
    const validation = this.validateTemplate({
      htmlContent: data.htmlContent,
      textContent: data.textContent,
      subjectLine: data.subjectLine,
    });

    if (!validation.valid) {
      throw new Error(`Template validation failed: ${validation.errors.join(', ')}`);
    }

    // Create template with variables and initial version in transaction
    const template = await prisma.$transaction(async (tx) => {
      const newTemplate = await tx.emailTemplate.create({
        data: {
          key: data.key,
          name: data.name,
          description: data.description,
          category: data.category,
          subjectLine: data.subjectLine,
          htmlContent: data.htmlContent,
          textContent: data.textContent,
          isSystem: false, // User-created templates are never system templates
          isActive: data.isActive ?? true,
          createdByUserId: userId,
          variables: data.variables
            ? {
                create: data.variables,
              }
            : undefined,
        },
        include: {
          variables: true,
        },
      });

      // Create initial version
      await tx.emailTemplateVersion.create({
        data: {
          templateId: newTemplate.id,
          versionNumber: 1,
          subjectLine: data.subjectLine,
          htmlContent: data.htmlContent,
          textContent: data.textContent,
          changeNotes: 'Initial version',
          createdByUserId: userId,
        },
      });

      return newTemplate;
    });

    logger.info(`Email template created: ${template.key} by user ${userId}`);

    // Clear email service cache for this template
    emailService.clearDatabaseCache(template.key);

    return template;
  }

  /**
   * Update an email template
   */
  async update(id: string, data: UpdateEmailTemplateDto, userId: string) {
    const existing = await this.getById(id);

    // Validate template content if provided
    if (data.htmlContent || data.textContent) {
      const validation = this.validateTemplate({
        htmlContent: data.htmlContent ?? existing.htmlContent,
        textContent: data.textContent ?? existing.textContent,
        subjectLine: data.subjectLine ?? existing.subjectLine,
      });

      if (!validation.valid) {
        throw new Error(`Template validation failed: ${validation.errors.join(', ')}`);
      }
    }

    // Update template and create new version if content changed
    const template = await prisma.$transaction(async (tx) => {
      // Update template
      const updated = await tx.emailTemplate.update({
        where: { id },
        data: {
          ...(data.name && { name: data.name }),
          ...(data.description !== undefined && { description: data.description }),
          ...(data.category && { category: data.category }),
          ...(data.subjectLine && { subjectLine: data.subjectLine }),
          ...(data.htmlContent && { htmlContent: data.htmlContent }),
          ...(data.textContent && { textContent: data.textContent }),
          ...(data.isActive !== undefined && { isActive: data.isActive }),
          updatedByUserId: userId,
          // Handle variables update
          ...(data.variables && {
            variables: {
              deleteMany: {},
              create: data.variables,
            },
          }),
        },
        include: {
          variables: true,
        },
      });

      // Create new version if content changed
      if (data.subjectLine || data.htmlContent || data.textContent) {
        const latestVersion = await tx.emailTemplateVersion.findFirst({
          where: { templateId: id },
          orderBy: { versionNumber: 'desc' },
        });

        const nextVersionNumber = (latestVersion?.versionNumber ?? 0) + 1;

        await tx.emailTemplateVersion.create({
          data: {
            templateId: id,
            versionNumber: nextVersionNumber,
            subjectLine: data.subjectLine ?? existing.subjectLine,
            htmlContent: data.htmlContent ?? existing.htmlContent,
            textContent: data.textContent ?? existing.textContent,
            changeNotes: `Updated via admin interface`,
            createdByUserId: userId,
          },
        });

        logger.info(`Created version ${nextVersionNumber} for template ${existing.key}`);
      }

      return updated;
    });

    logger.info(`Email template updated: ${existing.key} by user ${userId}`);

    // Clear email service cache
    emailService.clearDatabaseCache(existing.key);

    return template;
  }

  /**
   * Delete an email template
   */
  async delete(id: string) {
    const existing = await this.getById(id);

    if (existing.isSystem) {
      throw new Error('Cannot delete system templates');
    }

    await prisma.emailTemplate.delete({
      where: { id },
    });

    logger.info(`Email template deleted: ${existing.key}`);

    // Clear email service cache
    emailService.clearDatabaseCache(existing.key);
  }

  /**
   * Get version history for a template
   */
  async getVersions(templateId: string) {
    const versions = await prisma.emailTemplateVersion.findMany({
      where: { templateId },
      orderBy: { versionNumber: 'desc' },
      include: {
        createdBy: {
          select: { id: true, name: true, email: true },
        },
      },
    });

    return versions;
  }

  /**
   * Get a specific version
   */
  async getVersion(templateId: string, versionNumber: number) {
    const version = await prisma.emailTemplateVersion.findUnique({
      where: {
        templateId_versionNumber: {
          templateId,
          versionNumber,
        },
      },
      include: {
        createdBy: {
          select: { id: true, name: true, email: true },
        },
      },
    });

    if (!version) {
      throw new Error('Version not found');
    }

    return version;
  }

  /**
   * Rollback to a previous version
   */
  async rollbackToVersion(templateId: string, data: RollbackToVersionDto, userId: string) {
    const template = await this.getById(templateId);
    const targetVersion = await this.getVersion(templateId, data.versionNumber);

    // Create new version with content from target version
    const result = await prisma.$transaction(async (tx) => {
      const latestVersion = await tx.emailTemplateVersion.findFirst({
        where: { templateId },
        orderBy: { versionNumber: 'desc' },
      });

      const nextVersionNumber = (latestVersion?.versionNumber ?? 0) + 1;

      // Update template to target version content
      const updated = await tx.emailTemplate.update({
        where: { id: templateId },
        data: {
          subjectLine: targetVersion.subjectLine,
          htmlContent: targetVersion.htmlContent,
          textContent: targetVersion.textContent,
          updatedByUserId: userId,
        },
      });

      // Create new version (rollback creates a new version, doesn't revert history)
      await tx.emailTemplateVersion.create({
        data: {
          templateId,
          versionNumber: nextVersionNumber,
          subjectLine: targetVersion.subjectLine,
          htmlContent: targetVersion.htmlContent,
          textContent: targetVersion.textContent,
          changeNotes: data.changeNotes ?? `Rolled back to version ${data.versionNumber}`,
          createdByUserId: userId,
        },
      });

      return updated;
    });

    logger.info(`Template ${template.key} rolled back to version ${data.versionNumber} by user ${userId}`);

    // Clear email service cache
    emailService.clearDatabaseCache(template.key);

    return result;
  }

  /**
   * Validate template syntax
   */
  validateTemplate(data: ValidateTemplateDto): ValidationResult {
    const errors: string[] = [];
    const warnings: string[] = [];
    const variables = new Set<string>();

    // Extract variables from content
    const variableRegex = /\{\{([A-Z_]+)\}\}/g;
    const conditionalRegex = /\{\{#if\s+([A-Z_]+)\}\}|\{\{\/if\}\}/g;

    // Check HTML content
    let match;
    while ((match = variableRegex.exec(data.htmlContent)) !== null) {
      variables.add(match[1]);
    }
    while ((match = conditionalRegex.exec(data.htmlContent)) !== null) {
      if (match[1]) variables.add(match[1]);
    }

    // Check text content
    while ((match = variableRegex.exec(data.textContent)) !== null) {
      variables.add(match[1]);
    }
    while ((match = conditionalRegex.exec(data.textContent)) !== null) {
      if (match[1]) variables.add(match[1]);
    }

    // Check subject line if provided
    if (data.subjectLine) {
      while ((match = variableRegex.exec(data.subjectLine)) !== null) {
        variables.add(match[1]);
      }
    }

    // Check for unmatched conditionals
    const ifCount = (data.htmlContent.match(/\{\{#if/g) || []).length;
    const endifCount = (data.htmlContent.match(/\{\{\/if\}\}/g) || []).length;
    if (ifCount !== endifCount) {
      errors.push('Unmatched {{#if}} conditional blocks in HTML content');
    }

    const ifCountText = (data.textContent.match(/\{\{#if/g) || []).length;
    const endifCountText = (data.textContent.match(/\{\{\/if\}\}/g) || []).length;
    if (ifCountText !== endifCountText) {
      errors.push('Unmatched {{#if}} conditional blocks in text content');
    }

    // Warn if no variables found
    if (variables.size === 0) {
      warnings.push('No template variables found');
    }

    return {
      valid: errors.length === 0,
      errors,
      warnings,
      extractedVariables: Array.from(variables),
    };
  }

  /**
   * Send test email
   */
  async sendTestEmail(templateId: string, data: SendTestEmailDto, userId: string) {
    const template = await this.getById(templateId);

    try {
      // Process template with test data
      let htmlContent = template.htmlContent;
      let textContent = template.textContent;
      let subjectLine = template.subjectLine;

      // Replace variables
      for (const [key, value] of Object.entries(data.testData)) {
        const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
        htmlContent = htmlContent.replace(regex, value);
        textContent = textContent.replace(regex, value);
        subjectLine = subjectLine.replace(regex, value);
      }

      // Handle conditionals (simple implementation)
      for (const [key, value] of Object.entries(data.testData)) {
        const ifRegex = new RegExp(`\\{\\{#if\\s+${key}\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}`, 'g');
        const shouldShow = value && value !== 'false' && value !== '0';
        htmlContent = htmlContent.replace(ifRegex, shouldShow ? '$1' : '');
        textContent = textContent.replace(ifRegex, shouldShow ? '$1' : '');
      }

      // Send email via email service
      const result = await emailService.sendEmail({
        to: data.recipientEmail,
        subject: subjectLine,
        text: textContent,
        html: htmlContent,
      });

      // Log test email
      await prisma.emailTemplateTestLog.create({
        data: {
          templateId,
          recipientEmail: data.recipientEmail,
          testData: data.testData as Prisma.InputJsonValue,
          success: true,
          messageId: result.messageId,
          sentByUserId: userId,
        },
      });

      logger.info(`Test email sent for template ${template.key} to ${data.recipientEmail}`);

      return { success: true, messageId: result.messageId };
    } catch (error) {
      // Log failed test
      await prisma.emailTemplateTestLog.create({
        data: {
          templateId,
          recipientEmail: data.recipientEmail,
          testData: data.testData as Prisma.InputJsonValue,
          success: false,
          errorMessage: error instanceof Error ? error.message : String(error),
          sentByUserId: userId,
        },
      });

      logger.error(`Test email failed for template ${template.key}: ${error}`);

      throw error;
    }
  }

  /**
   * Get test logs for a template
   */
  async getTestLogs(templateId: string, limit = 10) {
    const logs = await prisma.emailTemplateTestLog.findMany({
      where: { templateId },
      orderBy: { sentAt: 'desc' },
      take: limit,
      include: {
        sentBy: {
          select: { id: true, name: true, email: true },
        },
      },
    });

    return logs;
  }
}

export const emailTemplatesService = new EmailTemplatesService();
