/**
 * Template Service
 * Notification template management with i18n support.
 *
 * @module Notifications
 */

import { Repository } from 'typeorm';
import {
  NotificationTemplate,
  TemplateTranslation,
  TemplateCategory,
} from '../entities/template.entity';
import { ChannelType } from '../entities/channel.entity';
import { ServiceContext } from './notification.service';

export interface CreateTemplateDto {
  code: string;
  name: string;
  description?: string;
  category?: TemplateCategory;
  channelType: ChannelType;
  subject?: string;
  bodyTemplate?: string;
  bodyHtml?: string;
  availableVariables?: string[];
  defaultLocale?: string;
  isActive?: boolean;
  isSystem?: boolean;
}

export interface UpdateTemplateDto {
  name?: string;
  description?: string;
  category?: TemplateCategory;
  subject?: string;
  bodyTemplate?: string;
  bodyHtml?: string;
  availableVariables?: string[];
  defaultLocale?: string;
  isActive?: boolean;
}

export interface CreateTranslationDto {
  locale: string;
  subject?: string;
  bodyTemplate?: string;
  bodyHtml?: string;
  isActive?: boolean;
}

export interface TemplateFilters {
  category?: TemplateCategory;
  channelType?: ChannelType;
  isActive?: boolean;
  isSystem?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

export interface RenderedTemplate {
  subject: string;
  body: string;
  bodyHtml?: string;
}

export class TemplateService {
  constructor(
    private readonly templateRepository: Repository<NotificationTemplate>,
    private readonly translationRepository: Repository<TemplateTranslation>,
  ) {}

  /**
   * Create a new template
   */
  async create(
    ctx: ServiceContext,
    dto: CreateTemplateDto,
  ): Promise<NotificationTemplate> {
    // Check for duplicate code
    const existing = await this.findByCode(ctx, dto.code, dto.channelType);
    if (existing) {
      throw new Error(`Template with code '${dto.code}' already exists for channel '${dto.channelType}'`);
    }

    const template = this.templateRepository.create({
      tenantId: ctx.tenantId,
      ...dto,
      createdBy: ctx.userId,
    });

    return this.templateRepository.save(template);
  }

  /**
   * Find template by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<NotificationTemplate | null> {
    return this.templateRepository.findOne({
      where: [
        { id, tenantId: ctx.tenantId },
        { id, tenantId: null as any }, // System templates
      ],
      relations: ['translations'],
    });
  }

  /**
   * Find template by code and channel type
   */
  async findByCode(
    ctx: ServiceContext,
    code: string,
    channelType: ChannelType,
  ): Promise<NotificationTemplate | null> {
    return this.templateRepository.findOne({
      where: [
        { code, channelType, tenantId: ctx.tenantId },
        { code, channelType, tenantId: null as any }, // System templates
      ],
      relations: ['translations'],
    });
  }

  /**
   * Find all templates with filters
   */
  async findAll(
    ctx: ServiceContext,
    filters: TemplateFilters,
  ): Promise<{ data: NotificationTemplate[]; total: number; page: number; limit: number }> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;
    const skip = (page - 1) * limit;

    const queryBuilder = this.templateRepository
      .createQueryBuilder('t')
      .leftJoinAndSelect('t.translations', 'translations')
      .where('(t.tenant_id = :tenantId OR t.tenant_id IS NULL)', { tenantId: ctx.tenantId });

    if (filters.category) {
      queryBuilder.andWhere('t.category = :category', { category: filters.category });
    }

    if (filters.channelType) {
      queryBuilder.andWhere('t.channel_type = :channelType', { channelType: filters.channelType });
    }

    if (filters.isActive !== undefined) {
      queryBuilder.andWhere('t.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.isSystem !== undefined) {
      queryBuilder.andWhere('t.is_system = :isSystem', { isSystem: filters.isSystem });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(t.code ILIKE :search OR t.name ILIKE :search OR t.description ILIKE :search)',
        { search: `%${filters.search}%` },
      );
    }

    const [data, total] = await queryBuilder
      .orderBy('t.is_system', 'DESC')
      .addOrderBy('t.code', 'ASC')
      .skip(skip)
      .take(limit)
      .getManyAndCount();

    return { data, total, page, limit };
  }

  /**
   * Update a template
   */
  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateTemplateDto,
  ): Promise<NotificationTemplate | null> {
    const template = await this.findById(ctx, id);
    if (!template) {
      return null;
    }

    // Cannot modify system templates from tenant context
    if (template.isSystem && template.tenantId === null) {
      throw new Error('Cannot modify system templates');
    }

    Object.assign(template, dto, { updatedBy: ctx.userId });
    template.version += 1;

    return this.templateRepository.save(template);
  }

  /**
   * Delete a template
   */
  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const template = await this.findById(ctx, id);
    if (!template) {
      return false;
    }

    // Cannot delete system templates
    if (template.isSystem && template.tenantId === null) {
      throw new Error('Cannot delete system templates');
    }

    const result = await this.templateRepository.delete({
      id,
      tenantId: ctx.tenantId,
    });

    return (result.affected ?? 0) > 0;
  }

  /**
   * Activate/deactivate a template
   */
  async setActive(
    ctx: ServiceContext,
    id: string,
    isActive: boolean,
  ): Promise<NotificationTemplate | null> {
    return this.update(ctx, id, { isActive });
  }

  /**
   * Add a translation to a template
   */
  async addTranslation(
    ctx: ServiceContext,
    templateId: string,
    dto: CreateTranslationDto,
  ): Promise<TemplateTranslation> {
    const template = await this.findById(ctx, templateId);
    if (!template) {
      throw new Error('Template not found');
    }

    // Check for existing translation
    const existing = await this.translationRepository.findOne({
      where: { templateId, locale: dto.locale },
    });

    if (existing) {
      throw new Error(`Translation for locale '${dto.locale}' already exists`);
    }

    const translation = this.translationRepository.create({
      templateId,
      ...dto,
    });

    return this.translationRepository.save(translation);
  }

  /**
   * Update a translation
   */
  async updateTranslation(
    ctx: ServiceContext,
    templateId: string,
    locale: string,
    dto: Partial<CreateTranslationDto>,
  ): Promise<TemplateTranslation | null> {
    const template = await this.findById(ctx, templateId);
    if (!template) {
      throw new Error('Template not found');
    }

    const translation = await this.translationRepository.findOne({
      where: { templateId, locale },
    });

    if (!translation) {
      return null;
    }

    Object.assign(translation, dto);
    return this.translationRepository.save(translation);
  }

  /**
   * Delete a translation
   */
  async deleteTranslation(
    ctx: ServiceContext,
    templateId: string,
    locale: string,
  ): Promise<boolean> {
    const template = await this.findById(ctx, templateId);
    if (!template) {
      throw new Error('Template not found');
    }

    const result = await this.translationRepository.delete({
      templateId,
      locale,
    });

    return (result.affected ?? 0) > 0;
  }

  /**
   * Get translation for a specific locale
   */
  async getTranslation(
    templateId: string,
    locale: string,
  ): Promise<TemplateTranslation | null> {
    return this.translationRepository.findOne({
      where: { templateId, locale, isActive: true },
    });
  }

  /**
   * Render a template with variables
   */
  async render(
    ctx: ServiceContext,
    code: string,
    channelType: ChannelType,
    variables: Record<string, any>,
    locale?: string,
  ): Promise<RenderedTemplate> {
    const template = await this.findByCode(ctx, code, channelType);
    if (!template) {
      throw new Error(`Template not found: ${code} (${channelType})`);
    }

    if (!template.isActive) {
      throw new Error(`Template is inactive: ${code}`);
    }

    let subject = template.subject || '';
    let body = template.bodyTemplate || '';
    let bodyHtml = template.bodyHtml;

    // Try to get translation if locale is specified
    if (locale && locale !== template.defaultLocale) {
      const translation = await this.getTranslation(template.id, locale);
      if (translation) {
        subject = translation.subject || subject;
        body = translation.bodyTemplate || body;
        bodyHtml = translation.bodyHtml || bodyHtml;
      }
    }

    // Render variables
    subject = this.renderVariables(subject, variables);
    body = this.renderVariables(body, variables);
    if (bodyHtml) {
      bodyHtml = this.renderVariables(bodyHtml, variables);
    }

    return { subject, body, bodyHtml };
  }

  /**
   * Replace variables in a template string
   */
  private renderVariables(template: string, variables: Record<string, any>): string {
    return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
      return variables[key] !== undefined ? String(variables[key]) : match;
    });
  }

  /**
   * Validate template variables
   */
  async validateVariables(
    ctx: ServiceContext,
    code: string,
    channelType: ChannelType,
    variables: Record<string, any>,
  ): Promise<{ valid: boolean; missing: string[]; extra: string[] }> {
    const template = await this.findByCode(ctx, code, channelType);
    if (!template) {
      throw new Error(`Template not found: ${code} (${channelType})`);
    }

    const requiredVariables = template.availableVariables || [];
    const providedVariables = Object.keys(variables);

    const missing = requiredVariables.filter((v) => !providedVariables.includes(v));
    const extra = providedVariables.filter((v) => !requiredVariables.includes(v));

    return {
      valid: missing.length === 0,
      missing,
      extra,
    };
  }

  /**
   * Clone a template
   */
  async clone(
    ctx: ServiceContext,
    id: string,
    newCode: string,
  ): Promise<NotificationTemplate> {
    const template = await this.findById(ctx, id);
    if (!template) {
      throw new Error('Template not found');
    }

    // Create new template
    const cloned = await this.create(ctx, {
      code: newCode,
      name: `${template.name} (Copy)`,
      description: template.description,
      category: template.category as TemplateCategory,
      channelType: template.channelType,
      subject: template.subject,
      bodyTemplate: template.bodyTemplate,
      bodyHtml: template.bodyHtml,
      availableVariables: [...(template.availableVariables || [])],
      defaultLocale: template.defaultLocale,
      isActive: false, // Start as inactive
      isSystem: false,
    });

    // Clone translations
    if (template.translations && template.translations.length > 0) {
      for (const translation of template.translations) {
        await this.addTranslation(ctx, cloned.id, {
          locale: translation.locale,
          subject: translation.subject,
          bodyTemplate: translation.bodyTemplate,
          bodyHtml: translation.bodyHtml,
          isActive: translation.isActive,
        });
      }
    }

    return this.findById(ctx, cloned.id) as Promise<NotificationTemplate>;
  }

  /**
   * Get templates by channel type
   */
  async findByChannelType(
    ctx: ServiceContext,
    channelType: ChannelType,
  ): Promise<NotificationTemplate[]> {
    return this.templateRepository.find({
      where: [
        { channelType, tenantId: ctx.tenantId, isActive: true },
        { channelType, tenantId: null as any, isActive: true },
      ],
      relations: ['translations'],
      order: { code: 'ASC' },
    });
  }

  /**
   * Get templates by category
   */
  async findByCategory(
    ctx: ServiceContext,
    category: TemplateCategory,
  ): Promise<NotificationTemplate[]> {
    return this.templateRepository.find({
      where: [
        { category, tenantId: ctx.tenantId, isActive: true },
        { category, tenantId: null as any, isActive: true },
      ],
      relations: ['translations'],
      order: { code: 'ASC' },
    });
  }

  /**
   * Preview a template with sample variables
   */
  async preview(
    ctx: ServiceContext,
    id: string,
    sampleVariables?: Record<string, any>,
    locale?: string,
  ): Promise<RenderedTemplate> {
    const template = await this.findById(ctx, id);
    if (!template) {
      throw new Error('Template not found');
    }

    // Generate sample variables if not provided
    const variables = sampleVariables || this.generateSampleVariables(template.availableVariables || []);

    return this.render(ctx, template.code, template.channelType, variables, locale);
  }

  /**
   * Generate sample variables for preview
   */
  private generateSampleVariables(variableNames: string[]): Record<string, any> {
    const samples: Record<string, string> = {};
    for (const name of variableNames) {
      samples[name] = `[${name}]`;
    }
    return samples;
  }
}
