import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { IiosInboxState } from '@prisma/client';
import type { IiosPlatformPorts } from '@insignia/iios-contracts';
import { PrismaService } from '../prisma/prisma.service';
import { PLATFORM_PORTS } from '../platform/platform-ports';
import { decideOrThrow } from '../platform/fail-closed';
import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver';

/** Allowed inbox state transitions. */
const TRANSITIONS: Record<IiosInboxState, IiosInboxState[]> = {
  OPEN: ['SNOOZED', 'DONE', 'STALE', 'ARCHIVED'],
  SNOOZED: ['OPEN', 'DONE', 'STALE', 'ARCHIVED'],
  DONE: ['OPEN', 'ARCHIVED'],
  STALE: ['OPEN', 'ARCHIVED'],
  ARCHIVED: [],
  CANCELLED: [],
};

@Injectable()
export class InboxService {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(PLATFORM_PORTS) private readonly ports: IiosPlatformPorts,
    private readonly actors: ActorResolver,
  ) {}

  /** List the caller's items (optionally filtered by state). Fail-closed. */
  async list(principal: MessagePrincipal, opts: { state?: IiosInboxState } = {}) {
    await decideOrThrow(this.ports, { action: 'iios.inbox.list', scope: principal });
    const scope = await this.actors.resolveScope(principal);
    const actor = await this.actors.resolveActor(scope.id, principal);
    return this.prisma.iiosInboxItem.findMany({
      where: { ownerActorId: actor.id, ...(opts.state ? { state: opts.state } : {}) },
      orderBy: [{ priority: 'asc' }, { updatedAt: 'desc' }],
    });
  }

  /** Transition one of the caller's items. Validates the state machine + fails closed. */
  async transition(
    itemId: string,
    principal: MessagePrincipal,
    toState: IiosInboxState,
    reason?: string,
  ) {
    const item = await this.prisma.iiosInboxItem.findUnique({ where: { id: itemId } });
    if (!item) throw new NotFoundException('inbox item not found');
    await decideOrThrow(this.ports, { action: 'iios.inbox.transition', itemId, scopeId: item.scopeId });

    const actor = await this.actors.resolveActor(item.scopeId, principal);
    if (item.ownerActorId !== actor.id) throw new ForbiddenException('not your inbox item');
    if (!TRANSITIONS[item.state].includes(toState)) {
      throw new BadRequestException(`illegal transition ${item.state} -> ${toState}`);
    }

    const [updated] = await this.prisma.$transaction([
      this.prisma.iiosInboxItem.update({ where: { id: itemId }, data: { state: toState } }),
      this.prisma.iiosInboxItemStateHistory.create({
        data: { inboxItemId: itemId, fromState: item.state, toState, actorId: actor.id, reasonCode: reason },
      }),
    ]);
    return updated;
  }
}
