import { readdir } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { loadFleetRoster } from '../commands/fleet.js';
import { loadProfiles } from '../commands/fleet-profiles.js';
import {
  provisionInteractionService,
  readInteractionServiceProfile,
} from './interaction-service-profile.js';

export type FleetArtifactDisposition =
  | 'v1-fixture'
  | 'canonical-profile'
  | 'canonical-service-policy';

export interface ShippedFleetArtifactDisposition {
  readonly path: string;
  readonly disposition: FleetArtifactDisposition;
}

export interface ValidateShippedFleetArtifactDispositionsOptions {
  readonly frameworkFleet: string;
  readonly rolesDir?: string;
  readonly overrideDir?: string;
}

/**
 * The M0 inventory in executable form. Every shipped fleet YAML asset is either
 * a deliberately retained v1 fixture or validated through its canonical loader.
 */
export const SHIPPED_FLEET_ARTIFACT_DISPOSITIONS: readonly ShippedFleetArtifactDisposition[] = [
  { path: 'examples/coding.yaml', disposition: 'v1-fixture' },
  { path: 'examples/general.yaml', disposition: 'v1-fixture' },
  { path: 'examples/hybrid.yaml', disposition: 'v1-fixture' },
  { path: 'examples/local-canary.yaml', disposition: 'v1-fixture' },
  { path: 'examples/minimal.yaml', disposition: 'v1-fixture' },
  { path: 'examples/operator-interaction.yaml', disposition: 'v1-fixture' },
  { path: 'examples/research.yaml', disposition: 'v1-fixture' },
  { path: 'profiles/business.yaml', disposition: 'canonical-profile' },
  { path: 'profiles/marketing.yaml', disposition: 'canonical-profile' },
  { path: 'profiles/personal-assistant.yaml', disposition: 'canonical-profile' },
  { path: 'profiles/research.yaml', disposition: 'canonical-profile' },
  { path: 'profiles/software-delivery.yaml', disposition: 'canonical-profile' },
  { path: 'services/operator-interaction.yaml', disposition: 'canonical-service-policy' },
];

/**
 * Fail closed when a fleet YAML asset is added or removed without a disposition.
 * This keeps legacy v1 compatibility explicit instead of silently accepting new
 * unresolved classes outside the shared resolver.
 */
export async function validateShippedFleetArtifactDispositions(
  options: ValidateShippedFleetArtifactDispositionsOptions,
): Promise<readonly ShippedFleetArtifactDisposition[]> {
  await assertEveryShippedArtifactIsDeclared(options.frameworkFleet);

  const examples = SHIPPED_FLEET_ARTIFACT_DISPOSITIONS.filter(
    ({ disposition }): boolean => disposition === 'v1-fixture',
  );
  for (const artifact of examples) {
    const roster = await loadFleetRoster(join(options.frameworkFleet, artifact.path));
    if (roster.version !== 1) {
      throw new Error(`v1 fixture ${artifact.path} must declare version: 1`);
    }
  }

  const profilesDir = join(options.frameworkFleet, 'profiles');
  const profiles = await loadProfiles({
    profilesDir,
    rolesDir: options.rolesDir ?? join(options.frameworkFleet, 'roles'),
    overrideDir: options.overrideDir ?? join(options.frameworkFleet, 'roles.local'),
  });
  const declaredProfiles = SHIPPED_FLEET_ARTIFACT_DISPOSITIONS.filter(
    ({ disposition }): boolean => disposition === 'canonical-profile',
  ).map(({ path }): string => basename(path, '.yaml'));
  const resolvedProfiles = new Set(profiles.map(({ id }): string => id));
  for (const profileId of declaredProfiles) {
    if (!resolvedProfiles.has(profileId)) {
      throw new Error(`declared canonical profile ${profileId} did not resolve`);
    }
  }

  const servicePolicies = SHIPPED_FLEET_ARTIFACT_DISPOSITIONS.filter(
    ({ disposition }): boolean => disposition === 'canonical-service-policy',
  );
  for (const artifact of servicePolicies) {
    const serviceProfile = await readInteractionServiceProfile(
      join(options.frameworkFleet, artifact.path),
    );
    provisionInteractionService(serviceProfile, { agentName: 'interaction-example' });
  }

  return SHIPPED_FLEET_ARTIFACT_DISPOSITIONS;
}

async function assertEveryShippedArtifactIsDeclared(frameworkFleet: string): Promise<void> {
  const declared = new Set(SHIPPED_FLEET_ARTIFACT_DISPOSITIONS.map(({ path }): string => path));
  const shipped = await listShippedFleetArtifactPaths(frameworkFleet);

  for (const path of shipped) {
    if (!declared.has(path)) {
      throw new Error(`undeclared shipped fleet artifact: ${path}`);
    }
  }
  for (const path of declared) {
    if (!shipped.has(path)) {
      throw new Error(`declared shipped fleet artifact is missing: ${path}`);
    }
  }
}

async function listShippedFleetArtifactPaths(frameworkFleet: string): Promise<Set<string>> {
  const directories = ['examples', 'profiles', 'services'];
  const paths = new Set<string>();
  for (const directory of directories) {
    const files = await readdir(join(frameworkFleet, directory));
    for (const file of files) {
      if (file.endsWith('.yaml') || file.endsWith('.yml')) paths.add(`${directory}/${file}`);
    }
  }
  return paths;
}
