import * as fs from 'node:fs';
import * as path from 'node:path';
import { packageNameToPath } from './package-store.js';
import { maxSemver } from './versioning.js';

type JsonRecord = Record<string, unknown>;

export interface IRegistryVersionEntry {
  readonly tarballPath: string;
  readonly publishedAt: string;
  readonly publishedBy: string;
}

export interface IRegistryPackageEntry {
  readonly versions: Record<string, IRegistryVersionEntry>;
}

export interface IRegistryIndex {
  readonly packages: Record<string, IRegistryPackageEntry>;
}

function asRecord(value: unknown): JsonRecord | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  return value as JsonRecord;
}

function readRegistryIndex(indexPath: string): IRegistryIndex {
  if (!fs.existsSync(indexPath)) {
    return { packages: {} };
  }

  const parsed = JSON.parse(fs.readFileSync(indexPath, 'utf8')) as unknown;
  const root = asRecord(parsed);
  const packagesRecord = asRecord(root?.packages) ?? {};
  const packages: Record<string, IRegistryPackageEntry> = {};

  for (const [packageName, packageValue] of Object.entries(packagesRecord)) {
    const packageEntry = asRecord(packageValue);
    const versionsRecord = asRecord(packageEntry?.versions) ?? {};
    const versions: Record<string, IRegistryVersionEntry> = {};

    for (const [version, versionValue] of Object.entries(versionsRecord)) {
      const versionEntry = asRecord(versionValue);
      if (!versionEntry) continue;
      if (typeof versionEntry.tarballPath !== 'string') continue;
      if (typeof versionEntry.publishedAt !== 'string') continue;
      if (typeof versionEntry.publishedBy !== 'string') continue;
      versions[version] = {
        tarballPath: versionEntry.tarballPath,
        publishedAt: versionEntry.publishedAt,
        publishedBy: versionEntry.publishedBy,
      };
    }

    packages[packageName] = { versions };
  }

  return { packages };
}

function writeRegistryIndex(indexPath: string, index: IRegistryIndex): void {
  fs.mkdirSync(path.dirname(indexPath), { recursive: true });
  fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), 'utf8');
}

function resolveIndexPath(registryRoot: string): string {
  return path.join(registryRoot, 'index.json');
}

export function publishRegistryVersion(
  registryRoot: string,
  packageName: string,
  version: string,
  sourceTarballPath: string,
  publishedBy: string,
): { readonly registryTarballPath: string } {
  const indexPath = resolveIndexPath(registryRoot);
  const index = readRegistryIndex(indexPath);
  const packageEntry = index.packages[packageName] ?? { versions: {} };
  if (packageEntry.versions[version]) {
    throw new Error(`MX_VERSION_EXISTS: ${packageName}@${version} already published`);
  }

  const packageVersionDir = path.join(registryRoot, 'packages', packageNameToPath(packageName), version);
  fs.mkdirSync(packageVersionDir, { recursive: true });
  const registryTarballPath = path.join(packageVersionDir, 'package.tar.gz');
  fs.copyFileSync(sourceTarballPath, registryTarballPath);

  packageEntry.versions[version] = {
    tarballPath: registryTarballPath,
    publishedAt: new Date().toISOString(),
    publishedBy,
  };
  index.packages[packageName] = packageEntry;
  writeRegistryIndex(indexPath, index);

  return { registryTarballPath };
}

export function resolveRegistryTarball(
  registryRoot: string,
  packageName: string,
  requestedVersion?: string,
): { readonly version: string; readonly tarballPath: string } {
  const index = readRegistryIndex(resolveIndexPath(registryRoot));
  const packageEntry = index.packages[packageName];
  if (!packageEntry) {
    throw new Error(`MX_PACKAGE_NOT_FOUND: ${packageName}`);
  }

  const version = requestedVersion ?? maxSemver(Object.keys(packageEntry.versions));
  if (!version || !packageEntry.versions[version]) {
    throw new Error(`MX_PACKAGE_NOT_FOUND: ${packageName}@${requestedVersion ?? 'latest'}`);
  }

  return {
    version,
    tarballPath: packageEntry.versions[version]!.tarballPath,
  };
}

export function getLatestRegistryVersion(registryRoot: string, packageName: string): string | null {
  const index = readRegistryIndex(resolveIndexPath(registryRoot));
  const packageEntry = index.packages[packageName];
  if (!packageEntry) {
    return null;
  }
  return maxSemver(Object.keys(packageEntry.versions));
}
