/**
 * FTP/FTPS adapter — uploads the standalone bundle to an FTP host (shared hosting).
 * Auth via password (JSON credentials secret). Prefers FTPS (secure) when available.
 * Requires the optional `basic-ftp` dependency; if it isn't installed the deploy fails
 * with a clear, non-crashing message.
 */

import { HostingAdapter, DeployInput, DeployResult, parseCreds, optionalRequire, normalizeHost, resolveRemoteRoot } from './types';

/** Does `name` exist as a directory in the client's current remote directory? */
async function ftpDirExists(client: any, name: string): Promise<boolean> {
  try {
    const entries = await client.list();
    return entries.some((e: any) => e.name === name && (e.isDirectory ?? e.type === 2));
  } catch {
    // Can't list — assume it isn't there and publish to the login directory, which is
    // the safer guess (a wrong subfolder silently serves a 404 at the real URL).
    return false;
  }
}

export const ftpAdapter: HostingAdapter = {
  provider: 'ftp',

  async validate(connection, secret) {
    const ftpLib = optionalRequire('basic-ftp');
    if (!ftpLib) return { ok: false, error: 'Server missing "basic-ftp" dependency. Run: npm i basic-ftp' };
    // Accept a host pasted as a URL — otherwise DNS is handed the whole string and
    // fails with `getaddrinfo ENOTFOUND https://host/`.
    const { host, port: hostPort } = normalizeHost(connection.host);
    if (!host || !connection.username) return { ok: false, error: 'Host and username are required' };
    const port = connection.port || hostPort || 21;
    const creds = parseCreds(secret);
    const client = new ftpLib.Client(20000);
    try {
      await client.access({
        host, port, user: connection.username,
        password: creds.password, secure: creds.secure !== false,
      });
      return { ok: true };
    } catch (err: any) {
      // Retry once without TLS if the server doesn't support FTPS.
      try {
        await client.access({ host, port, user: connection.username, password: creds.password, secure: false });
        return { ok: true };
      } catch (e2: any) {
        return { ok: false, error: e2?.message || err?.message || 'FTP connection failed' };
      }
    } finally {
      client.close();
    }
  },

  async deploy({ bundle, connection, secret }: DeployInput): Promise<DeployResult> {
    const ftpLib = optionalRequire('basic-ftp');
    if (!ftpLib) throw new Error('Server missing "basic-ftp" dependency. Run: npm i basic-ftp');
    const { host, port: hostPort } = normalizeHost(connection.host);
    if (!host || !connection.username) throw new Error('FTP connection needs host and username.');
    const port = connection.port || hostPort || 21;

    const creds = parseCreds(secret);
    const remoteRoot = resolveRemoteRoot(connection.remotePath);
    const client = new ftpLib.Client(30000);
    try {
      try {
        await client.access({ host, port, user: connection.username, password: creds.password, secure: creds.secure !== false });
      } catch {
        await client.access({ host, port, user: connection.username, password: creds.password, secure: false });
      }

      let target = remoteRoot.path;
      if (target && !remoteRoot.explicit) {
        // Only a preference, not an instruction. Most shared-hosting FTP accounts log
        // straight into the web root, where ensureDir('public_html') would CREATE a
        // subfolder and publish the site to https://domain/public_html/ instead of /.
        // Descend only if the directory is really there.
        if (!(await ftpDirExists(client, target))) {
          console.log(`[FTP] "${target}" not found at the login directory — publishing to the login directory (it is already the web root).`);
          target = '';
        }
      }
      if (target) await client.ensureDir(target); // also leaves us inside it
      // Upload the whole standalone bundle into the current remote directory.
      await client.uploadFromDir(bundle.dir);
    } finally {
      client.close();
    }

    return { deployUrl: connection.baseUrl || '', providerDeployId: bundle.hash };
  },
};
