import { randomUUID } from 'node:crypto';
import { IsNotEmpty, IsString } from 'class-validator';
import type {
  HarnessActorContext,
  HarnessAuthState,
  HarnessCapability,
  HarnessCatalog,
  HarnessCatalogEntry,
  HarnessDescriptor,
  HarnessInputType,
  HarnessModelAvailability,
  HarnessSelection,
} from '@mosaicstack/types';
import type { ActorTenantScope } from '../auth/session-scope.js';

/**
 * Structured selection tuple accepted on `PUT /api/chat/preferences/selection`.
 *
 * The body is a STRUCTURED tuple (harness + provider + model), never a free-text
 * model string. With `ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })`
 * any extra property — including smuggled server-authority fields such as
 * `seatId`, `tenantId`, `userId`, `nativeSessionPath`, `executable`, `home`, `cwd` —
 * is rejected with 400. There is deliberately no field through which a caller can
 * name a scope; scope is derived on the server from the authenticated session.
 */
export class HarnessSelectionInputDto {
  @IsString()
  @IsNotEmpty()
  harnessId!: string;

  @IsString()
  @IsNotEmpty()
  providerId!: string;

  @IsString()
  @IsNotEmpty()
  modelId!: string;
}

/** Browser-safe harness summary — identity and capabilities only. */
export interface HarnessSummaryDto {
  readonly id: string;
  readonly displayName: string;
  readonly capabilities: readonly HarnessCapability[];
}

/** Browser-safe catalog entry — no executables, paths, secrets, or env. */
export interface HarnessCatalogEntryDto {
  readonly harnessId: string;
  readonly providerId: string;
  readonly modelId: string;
  readonly displayName: string;
  readonly reasoningCapability: boolean;
  readonly inputTypes: readonly HarnessInputType[];
  readonly authState: HarnessAuthState;
  readonly availability: HarnessModelAvailability;
}

/** Browser-safe catalog envelope. */
export interface HarnessCatalogDto {
  readonly harnessId: string;
  readonly version: string;
  readonly fingerprint: string;
  readonly models: readonly HarnessCatalogEntryDto[];
}

/** Response envelope for the caller's current selection (null when unset). */
export interface SelectionResponseDto {
  readonly selection: HarnessSelection | null;
}

/**
 * Derive a server-trusted {@link HarnessActorContext} for read operations from the
 * session-derived {@link ActorTenantScope}. All authority originates on the server;
 * nothing here is caller-supplied. A fresh correlation id is minted per call.
 */
export function readContextFromScope(scope: ActorTenantScope): HarnessActorContext {
  return {
    actorId: scope.userId,
    tenantId: scope.tenantId,
    seatId: scope.userId,
    correlationId: randomUUID(),
  };
}

/** Project a descriptor onto the browser-safe summary shape (whitelist by construction). */
export function toHarnessSummary(descriptor: HarnessDescriptor): HarnessSummaryDto {
  return {
    id: descriptor.id,
    displayName: descriptor.displayName,
    capabilities: [...descriptor.capabilities],
  };
}

/** Project a catalog onto the browser-safe shape (whitelist by construction). */
export function toSafeCatalog(catalog: HarnessCatalog): HarnessCatalogDto {
  return {
    harnessId: catalog.harnessId,
    version: catalog.version,
    fingerprint: catalog.fingerprint,
    models: catalog.models.map(toSafeCatalogEntry),
  };
}

function toSafeCatalogEntry(entry: HarnessCatalogEntry): HarnessCatalogEntryDto {
  return {
    harnessId: entry.harnessId,
    providerId: entry.providerId,
    modelId: entry.modelId,
    displayName: entry.displayName,
    reasoningCapability: entry.reasoningCapability,
    inputTypes: [...entry.inputTypes],
    authState: entry.authState,
    availability: entry.availability,
  };
}
