import { Injectable, Logger } from '@nestjs/common';
import { Action, SubjectType } from '@pac/types';
import type {
  PullStrategy,
  SyncContext,
  RawPullPage,
  RawPullPatientResult,
} from './adapter.interface';
import type { EmitsConfig } from '../assembler/assembler.schema';

/**
 * Mock 返回的"已 canonical 化"表 → emits 注册表。
 *
 * 真实 adapter 应跑 AssemblerEngine 从 yaml 拿 emits;mock 是 shortcut,
 * 直接列每个 canonical resource 对应的 emits 三件套。
 */
const MOCK_EMITS: Record<string, EmitsConfig> = {
  appointments: {
    action: Action.APPOINTMENT_CREATED,
    subjectType: SubjectType.APPOINTMENT,
    occurredAtField: 'occurredAt',
  },
};

/**
 * MockPullStrategy — 假宿主,不调任何 HTTP,直接合成数据。
 *
 * 用途:
 *   - 真宿主未接入前,验证 Pull / Reconcile 框架端到端走通
 *   - CI 测试不依赖外部
 *
 * 触发:host.pullConfig.adapter === 'mock' 时命中(见 PullStrategyRegistry)。
 *
 * **简化**:本 adapter 返回"已 canonical 化"的 rows(直接以 CanonicalResourceKey 做表名,
 * 字段已是 PAC 内部名)— 跳过 AssemblerEngine。
 * 真实 adapter 会返回 host 原生表(tb_xxx)+ pullConfig 指 assembler yaml 目录,由 orchestrator
 * 跑 AssemblerEngine。框架已为此预留(pullConfig.assemblerDir / pullConfig.adapter)。
 */
@Injectable()
export class MockPullStrategy implements PullStrategy {
  private readonly logger = new Logger(MockPullStrategy.name);
  readonly key = 'mock-pull';

  appliesTo(ctx: SyncContext): boolean {
    const cfg = ctx.pullConfig as { adapter?: string } | null;
    return cfg?.adapter === 'mock';
  }

  async pullPage(ctx: SyncContext, cursor: string | null): Promise<RawPullPage> {
    const opts = this.opts(ctx);
    const offset = cursor ? Number(cursor) : 0;
    const end = Math.min(offset + opts.pageSize, opts.totalPatients);
    if (offset >= opts.totalPatients) return { tables: {}, nextCursor: null };
    const tables = this.synthesizeTables(offset, end, opts);
    const nextCursor = end < opts.totalPatients ? String(end) : null;
    this.logger.debug(`mock pullPage offset=${offset}..${end} next=${nextCursor}`);
    return { tables, nextCursor, emitsPerResource: MOCK_EMITS };
  }

  async *pullWindow(
    ctx: SyncContext,
    startedAt: Date,
    endedAt: Date,
  ): AsyncIterable<RawPullPage> {
    const opts = this.opts(ctx);
    const tables = this.synthesizeTables(0, opts.totalPatients, opts);
    this.logger.debug(
      `mock pullWindow [${startedAt.toISOString()}..${endedAt.toISOString()}] → ${opts.totalPatients} patients`,
    );
    yield { tables, nextCursor: null, emitsPerResource: MOCK_EMITS };
  }

  async pullPatient(
    ctx: SyncContext,
    patientExternalId: string,
  ): Promise<RawPullPatientResult> {
    const opts = this.opts(ctx);
    const idx = Array.from({ length: opts.totalPatients }).findIndex(
      (_, i) => `mock-p${i + 1}` === patientExternalId,
    );
    if (idx < 0) return { tables: {}, hostNotFound: true };
    return { tables: this.synthesizeTables(idx, idx + 1, opts), emitsPerResource: MOCK_EMITS };
  }

  // ─────────────────────────────────────────────
  // 合成"已 canonical 化"的 tables(key = CanonicalResourceKey)
  // ─────────────────────────────────────────────
  private synthesizeTables(
    fromIdx: number,
    toIdx: number,
    opts: MockOpts,
  ): Record<string, unknown[]> {
    const patients: unknown[] = [];
    const appointments: unknown[] = [];

    for (let i = fromIdx; i < toIdx; i++) {
      const externalId = `mock-p${i + 1}`;
      const baseTime = new Date(Date.UTC(2026, 4, 1, 10, 0, 0));
      baseTime.setUTCDate(baseTime.getUTCDate() + i);

      patients.push({
        externalId,
        name: `测试患者${i + 1}`,
        phone: `1380000${String(1000 + i).padStart(4, '0')}`,
        gender: i % 2 === 0 ? 'M' : 'F',
        birthDate: `1980-${String((i % 12) + 1).padStart(2, '0')}-15`,
        clinicId: opts.clinicId,
        occurredAt: baseTime.toISOString(),
      });

      if (opts.withAppointments) {
        appointments.push({
          externalId: `${externalId}-appt-1`,
          patientExternalId: externalId,
          clinicId: opts.clinicId,
          appointmentAt: baseTime.toISOString(),
          occurredAt: baseTime.toISOString(),
          status: 'scheduled',
          doctorId: `mock-d${(i % 3) + 1}`,
          treatmentCategory: '复查',
        });
      }
    }

    const result: Record<string, unknown[]> = { patients };
    if (opts.withAppointments) result.appointments = appointments;
    return result;
  }

  private opts(ctx: SyncContext): MockOpts {
    const m = (ctx.pullConfig as { mock?: Partial<MockOpts> } | null)?.mock ?? {};
    return {
      pageSize: m.pageSize ?? 3,
      totalPatients: m.totalPatients ?? 6,
      withAppointments: m.withAppointments ?? true,
      clinicId: m.clinicId ?? 'mock-clinic-1',
    };
  }
}

interface MockOpts {
  pageSize: number;
  totalPatients: number;
  withAppointments: boolean;
  clinicId: string;
}
