import { Inject, Injectable } from '@nestjs/common';
import type { IiosPlatformPorts } from '@insignia/iios-contracts';
import { OutboundService } from '../adapters/outbound.service';
import { PLATFORM_PORTS } from '../platform/platform-ports';
import { decideOrThrow } from '../platform/fail-closed';
import { TemplateService } from './template.service';
import type { RenderedContent } from './template.renderer';
import { isInlineSource, type TemplateSource } from './template.model';

/** External-egress channels only. INTERNAL (in-app, no SMTP) delivery is the messaging module's job. */
export type ExternalChannel = 'EMAIL' | 'SMS';

/** An email attachment REF (bytes resolved by the provider at send time). */
export interface AttachmentRef { filename?: string; contentRef: string; mimeType?: string }

export interface SendTemplatedInput {
  source: TemplateSource;
  channel: ExternalChannel;
  target: string; // email address / phone number
  vars: Record<string, unknown>;
  scopeId?: string;
  locale?: string;
  idempotencyKey: string; // REQUIRED — e.g. "receipt:<stripe_session_id>"
  purpose?: string;
  attachments?: AttachmentRef[]; // EMAIL only
}

/**
 * The single entry point for a templated external send: render → OutboundService.send → provenance
 * stamped in one place (by construction, not caller convention). Rendering stays pure/testable;
 * OutboundService stays content-agnostic (it only records the provenance it is handed).
 */
@Injectable()
export class TemplatedSender {
  constructor(
    private readonly templates: TemplateService,
    private readonly outbound: OutboundService,
    @Inject(PLATFORM_PORTS) private readonly ports: IiosPlatformPorts,
  ) {}

  async sendTemplated(input: SendTemplatedInput) {
    // Inline HTML to a customer is more dangerous than a reviewed stored template — gate it apart,
    // so policy can permit stored sends while restricting ad-hoc ones. Fail-closed.
    const action = isInlineSource(input.source) ? 'iios.template.send.inline' : 'iios.template.send';
    await decideOrThrow(this.ports, { action, scopeId: input.scopeId, channel: input.channel });

    const { content, provenance } = await this.templates.render(input.source, input.vars, {
      channel: input.channel,
      locale: input.locale,
      scopeId: input.scopeId,
    });
    return this.outbound.send(
      input.channel,
      input.target,
      this.toPayload(input.channel, content, input.attachments),
      input.idempotencyKey,
      input.scopeId,
      input.purpose,
      provenance,
    );
  }

  /** Shape rendered content into the channel's egress envelope (EmailProvider reads subject/text/html). */
  private toPayload(channel: ExternalChannel, content: RenderedContent, attachments?: AttachmentRef[]): Record<string, unknown> {
    if (channel === 'EMAIL') {
      return { subject: content.subject, html: content.html, text: content.text, ...(attachments && attachments.length > 0 ? { attachments } : {}) };
    }
    return { text: content.text ?? content.subject ?? '' }; // SMS: text only
  }
}
