import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver';

/**
 * Simulated calendar adapter (P8). Models the external-calendar sync shape the
 * Atlas calls for — provider account, incremental sync cursor, external event ids
 * — deterministically and with NO network. A real provider (Google/Outlook/Zoom)
 * via the Capability Broker replaces this in P9.
 */
@Injectable()
export class CalendarAdapter {
  constructor(
    private readonly prisma: PrismaService,
    private readonly actors: ActorResolver,
  ) {}

  async connectProvider(principal: MessagePrincipal) {
    const scope = await this.actors.resolveScope(principal);
    const actor = await this.actors.resolveActor(scope.id, principal);
    const account = await this.prisma.iiosCalendarProviderAccount.create({
      data: { scopeId: scope.id, actorRefId: actor.id, providerType: 'SIMULATED', accountHandleToken: `sim:${actor.id}` },
    });
    await this.prisma.iiosCalendarSyncCursor.create({ data: { providerAccountId: account.id, status: 'IDLE' } });
    return account;
  }

  /**
   * Deterministically "pull" a stable set of external events (idempotent by
   * externalEventId) and advance the cursor. Re-running never duplicates events.
   */
  async syncOnce(providerAccountId: string) {
    const account = await this.prisma.iiosCalendarProviderAccount.findUnique({ where: { id: providerAccountId } });
    if (!account) throw new NotFoundException('provider account not found');

    const fixtures = [
      { externalEventId: `ext-${providerAccountId}-standup`, title: 'Daily standup', startAt: '2026-07-02T09:00:00.000Z', endAt: '2026-07-02T09:30:00.000Z' },
      { externalEventId: `ext-${providerAccountId}-review`, title: 'Design review', startAt: '2026-07-02T14:00:00.000Z', endAt: '2026-07-02T15:00:00.000Z' },
    ];
    for (const f of fixtures) {
      await this.prisma.iiosCalendarEvent.upsert({
        where: { providerAccountId_externalEventId: { providerAccountId, externalEventId: f.externalEventId } },
        create: {
          scopeId: account.scopeId,
          providerAccountId,
          externalEventId: f.externalEventId,
          title: f.title,
          startAt: new Date(f.startAt),
          endAt: new Date(f.endAt),
          status: 'CONFIRMED',
        },
        update: { title: f.title },
      });
    }

    const cursor = await this.prisma.iiosCalendarSyncCursor.findUniqueOrThrow({ where: { providerAccountId } });
    const next = String(Number(cursor.externalCursor ?? '0') + 1);
    await this.prisma.iiosCalendarSyncCursor.update({
      where: { providerAccountId },
      data: { externalCursor: next, lastSyncAt: new Date(), status: 'IDLE' },
    });
    return { pulled: fixtures.length, cursor: next };
  }

  async setAvailability(principal: MessagePrincipal, input: { startAt: string; endAt: string; status?: string; source?: string }) {
    const scope = await this.actors.resolveScope(principal);
    const actor = await this.actors.resolveActor(scope.id, principal);
    return this.prisma.iiosAvailabilityWindow.create({
      data: {
        scopeId: scope.id,
        actorRefId: actor.id,
        startAt: new Date(input.startAt),
        endAt: new Date(input.endAt),
        source: input.source ?? 'USER',
        status: input.status ?? 'ACTIVE',
      },
    });
  }

  /** Free/busy over a window: busy if any confirmed calendar event in scope overlaps. */
  async availability(principal: MessagePrincipal, window: { startAt: string; endAt: string }) {
    const scope = await this.actors.resolveScope(principal);
    const start = new Date(window.startAt);
    const end = new Date(window.endAt);
    const conflicts = await this.prisma.iiosCalendarEvent.findMany({
      where: { scopeId: scope.id, status: 'CONFIRMED', startAt: { lt: end }, endAt: { gt: start } },
    });
    return { free: conflicts.length === 0, conflicts };
  }
}
