import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { CommunicationTemplateRepository } from './repositories/communication-template.repository';
import {
  CreateCommunicationTemplateDto,
  UpdateCommunicationTemplateDto,
} from './dto';
import {
  CommunicationChannel,
  CommunicationContext,
  CommunicationTemplateEntity,
} from '../../entities/communication-template.entity';
import { TemplateRendererService } from './services/template-renderer.service';
import { QueryContextResolver } from './resolvers/query-context.resolver';
import { BookingContextResolver } from './resolvers/booking-context.resolver';
import { MovementSupplierContextResolver } from './resolvers/movement-supplier-context.resolver';
import { MovementCustomerContextResolver } from './resolvers/movement-customer-context.resolver';

/**
 * Business logic for the Communication Template master.
 *
 * Immutability rule: channel + context are frozen post-create. Any
 * attempt to change either is rejected — placeholders in the body
 * refer to a specific context's data bag; changing the axis would
 * silently break the template.
 *
 * Default flag: at most one is_default=true per tenant × channel ×
 * context (partial unique index in the migration). This service
 * clears the previous default in that bucket before setting a new
 * one so the API caller doesn't have to sequence two calls.
 */
@Injectable()
export class CommunicationTemplateService {
  constructor(
    private readonly repo: CommunicationTemplateRepository,
    private readonly renderer: TemplateRendererService,
    private readonly queryCtx: QueryContextResolver,
    private readonly bookingCtx: BookingContextResolver,
    private readonly movementSupplierCtx: MovementSupplierContextResolver,
    private readonly movementCustomerCtx: MovementCustomerContextResolver,
  ) {}

  async findAllForAdmin() {
    return this.repo.findAllForAdmin();
  }

  async findById(id: string): Promise<CommunicationTemplateEntity> {
    const t = await this.repo.findById(id);
    if (!t) throw new NotFoundException('Template not found');
    return t;
  }

  async findByChannelAndContext(
    channel: CommunicationChannel,
    context: CommunicationContext,
  ): Promise<CommunicationTemplateEntity[]> {
    return this.repo.findByChannelAndContext(channel, context);
  }

  async create(dto: CreateCommunicationTemplateDto) {
    if (dto.channel === 'WHATSAPP' && dto.subject) {
      throw new BadRequestException('WhatsApp templates must not carry a subject');
    }
    if (dto.channel === 'EMAIL' && !dto.subject) {
      throw new BadRequestException('Email templates require a subject');
    }
    if (dto.is_default) {
      await this.clearExistingDefault(dto.channel, dto.context);
    }
    const row = await this.repo.save({
      channel: dto.channel,
      context: dto.context,
      name: dto.name,
      subject: dto.subject ?? null,
      body_template: dto.body_template,
      is_default: dto.is_default ?? false,
      is_active: dto.is_active ?? true,
    } as any);
    return { id: (row as CommunicationTemplateEntity).id, message: 'Template created' };
  }

  async update(id: string, dto: UpdateCommunicationTemplateDto) {
    const existing = await this.findById(id);
    const nextChannel = existing.channel;
    if (nextChannel === 'WHATSAPP' && dto.subject) {
      throw new BadRequestException('WhatsApp templates must not carry a subject');
    }
    if (nextChannel === 'EMAIL' && dto.subject === '') {
      throw new BadRequestException('Email templates require a subject');
    }
    if (dto.is_default === true && !existing.is_default) {
      await this.clearExistingDefault(existing.channel, existing.context);
    }
    const patch: Partial<CommunicationTemplateEntity> = {};
    if (dto.name !== undefined) patch.name = dto.name;
    if (dto.subject !== undefined) patch.subject = dto.subject ?? null;
    if (dto.body_template !== undefined) patch.body_template = dto.body_template;
    if (dto.is_default !== undefined) patch.is_default = dto.is_default;
    if (dto.is_active !== undefined) patch.is_active = dto.is_active;
    await this.repo.update(id, patch);
    return { id, message: 'Template updated' };
  }

  async remove(id: string) {
    const existing = await this.findById(id);
    if (existing.is_default) {
      throw new ForbiddenException(
        'Cannot delete the default template. Mark another as default first.',
      );
    }
    await this.repo.softDelete(id);
    return { message: 'Template deleted' };
  }

  /**
   * Render a template against a subject record. `subjectId` shape
   * depends on the context: QUERY → queryId, BOOKING → bookingId,
   * MOVEMENT → movementDate (Phase D; not wired yet).
   */
  async render(id: string, subjectId: string): Promise<{ subject: string; body: string }> {
    const t = await this.findById(id);
    let data: Record<string, unknown>;
    switch (t.context) {
      case 'QUERY':
        data = await this.queryCtx.build(subjectId);
        break;
      case 'BOOKING':
        data = await this.bookingCtx.build(subjectId);
        break;
      case 'MOVEMENT_SUPPLIER':
        data = await this.movementSupplierCtx.build(subjectId);
        break;
      case 'MOVEMENT_CUSTOMER':
        data = await this.movementCustomerCtx.build(subjectId);
        break;
      default:
        throw new BadRequestException(
          `Rendering context ${t.context} is not wired yet`,
        );
    }
    const body = this.renderer.render(t.body_template, data);
    const subject = t.subject ? this.renderer.render(t.subject, data) : '';
    return { subject, body };
  }

  private async clearExistingDefault(
    channel: CommunicationChannel,
    context: CommunicationContext,
  ) {
    const rows = await this.repo.find({
      where: {
        channel,
        context,
        is_default: true,
        is_deleted: false,
      } as any,
    });
    for (const r of rows) {
      await this.repo.update((r as any).id, { is_default: false } as any);
    }
  }
}
