#!/usr/bin/env node
import { Command } from 'commander';
import { APP_NAME, APP_VERSION } from '@mcpctl/shared';
import { createConfigCommand } from './commands/config.js';
import { createStatusCommand } from './commands/status.js';
import { createGetCommand } from './commands/get.js';
import { createDescribeCommand } from './commands/describe.js';
import { createDeleteCommand } from './commands/delete.js';
import { createLogsCommand } from './commands/logs.js';
import { createApplyCommand } from './commands/apply.js';
import { createCreateCommand } from './commands/create.js';
import { createEditCommand } from './commands/edit.js';
import { createBackupCommand } from './commands/backup.js';
import { createLoginCommand, createLogoutCommand } from './commands/auth.js';
import { createAttachServerCommand, createDetachServerCommand, createApproveCommand } from './commands/project-ops.js';
import { createMcpCommand } from './commands/mcp.js';
import { createPatchCommand } from './commands/patch.js';
import { createConsoleCommand } from './commands/console/index.js';
import { createCacheCommand } from './commands/cache.js';
import { ApiClient, ApiError } from './api-client.js';
import { loadConfig } from './config/index.js';
import { loadCredentials } from './auth/index.js';
import { resolveNameOrId } from './commands/shared.js';

export function createProgram(): Command {
  const program = new Command()
    .name(APP_NAME)
    .description('Manage MCP servers like kubectl manages containers')
    .version(APP_VERSION, '-v, --version')
    .enablePositionalOptions()
    .option('--daemon-url <url>', 'mcplocal daemon URL')
    .option('--direct', 'bypass mcplocal and connect directly to mcpd')
    .option('-p, --project <name>', 'Target project for project commands');

  program.addCommand(createStatusCommand());
  program.addCommand(createLoginCommand());
  program.addCommand(createLogoutCommand());

  // Resolve target URL: --direct goes to mcpd, default goes to mcplocal
  const config = loadConfig();
  const creds = loadCredentials();
  const opts = program.opts();
  let baseUrl: string;
  if (opts.daemonUrl) {
    baseUrl = opts.daemonUrl as string;
  } else if (opts.direct) {
    baseUrl = config.mcpdUrl;
  } else {
    baseUrl = config.mcplocalUrl;
  }

  const client = new ApiClient({ baseUrl, token: creds?.token ?? undefined });

  program.addCommand(createConfigCommand(undefined, {
    client,
    credentialsDeps: {},
    log: (...args) => console.log(...args),
  }));

  const fetchResource = async (resource: string, nameOrId?: string, opts?: { project?: string; all?: boolean }): Promise<unknown[]> => {
    const projectName = opts?.project ?? program.opts().project as string | undefined;

    // Virtual resource: serverattachments (composed from project data)
    if (resource === 'serverattachments') {
      type ProjectWithServers = { name: string; id: string; servers?: Array<{ server: { name: string } }> };
      let projects: ProjectWithServers[];
      if (projectName) {
        const projectId = await resolveNameOrId(client, 'projects', projectName);
        const project = await client.get<ProjectWithServers>(`/api/v1/projects/${projectId}`);
        projects = [project];
      } else {
        projects = await client.get<ProjectWithServers[]>('/api/v1/projects');
      }
      const attachments: Array<{ project: string; server: string }> = [];
      for (const p of projects) {
        if (p.servers) {
          for (const ps of p.servers) {
            attachments.push({ server: ps.server.name, project: p.name });
          }
        }
      }
      return attachments;
    }

    // --project scoping for servers: show only attached servers
    if (!nameOrId && resource === 'servers' && projectName) {
      const projectId = await resolveNameOrId(client, 'projects', projectName);
      return client.get<unknown[]>(`/api/v1/projects/${projectId}/servers`);
    }

    // --project scoping for prompts and promptrequests
    if (!nameOrId && (resource === 'prompts' || resource === 'promptrequests')) {
      if (projectName) {
        return client.get<unknown[]>(`/api/v1/${resource}?project=${encodeURIComponent(projectName)}`);
      }
      // Default: global-only. --all (-A) shows everything.
      if (!opts?.all) {
        return client.get<unknown[]>(`/api/v1/${resource}?scope=global`);
      }
    }

    if (nameOrId) {
      // Glob pattern — use query param filtering
      if (nameOrId.includes('*')) {
        return client.get<unknown[]>(`/api/v1/${resource}?name=${encodeURIComponent(nameOrId)}`);
      }
      let id: string;
      try {
        id = await resolveNameOrId(client, resource, nameOrId);
      } catch {
        id = nameOrId;
      }
      const item = await client.get(`/api/v1/${resource}/${id}`);
      return [item];
    }
    return client.get<unknown[]>(`/api/v1/${resource}`);
  };

  const fetchSingleResource = async (resource: string, nameOrId: string): Promise<unknown> => {
    const projectName = program.opts().project as string | undefined;

    // Prompts: resolve within project scope (or global-only without --project)
    if (resource === 'prompts' || resource === 'promptrequests') {
      const scope = projectName
        ? `?project=${encodeURIComponent(projectName)}`
        : '?scope=global';
      const items = await client.get<Array<Record<string, unknown>>>(`/api/v1/${resource}${scope}`);
      const match = items.find((item) => item.name === nameOrId);
      if (!match) {
        throw new Error(`${resource.replace(/s$/, '')} '${nameOrId}' not found${projectName ? ` in project '${projectName}'` : ' (global scope). Use --project to specify a project'}`);
      }
      return client.get(`/api/v1/${resource}/${match.id as string}`);
    }

    let id: string;
    try {
      id = await resolveNameOrId(client, resource, nameOrId);
    } catch {
      id = nameOrId;
    }
    return client.get(`/api/v1/${resource}/${id}`);
  };

  program.addCommand(createGetCommand({
    fetchResource,
    log: (...args) => console.log(...args),
    getProject: () => program.opts().project as string | undefined,
    mcplocalUrl: config.mcplocalUrl,
  }));

  program.addCommand(createDescribeCommand({
    client,
    fetchResource: fetchSingleResource,
    fetchInspect: async (id: string) => client.get(`/api/v1/instances/${id}/inspect`),
    log: (...args) => console.log(...args),
    mcplocalUrl: config.mcplocalUrl,
  }));

  program.addCommand(createDeleteCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createLogsCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createCreateCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createEditCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createApplyCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createPatchCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  program.addCommand(createBackupCommand({
    client,
    log: (...args) => console.log(...args),
  }));

  const projectOpsDeps = {
    client,
    log: (...args: string[]) => console.log(...args),
    getProject: () => program.opts().project as string | undefined,
  };
  program.addCommand(createAttachServerCommand(projectOpsDeps), { hidden: true });
  program.addCommand(createDetachServerCommand(projectOpsDeps), { hidden: true });
  program.addCommand(createApproveCommand(projectOpsDeps));
  program.addCommand(createMcpCommand({
    getProject: () => program.opts().project as string | undefined,
  }), { hidden: true });

  program.addCommand(createConsoleCommand({
    getProject: () => program.opts().project as string | undefined,
  }));

  program.addCommand(createCacheCommand({
    log: (...args) => console.log(...args),
    mcplocalUrl: config.mcplocalUrl,
  }));

  return program;
}

// Run when invoked directly
const isDirectRun =
  typeof process !== 'undefined' &&
  process.argv[1] !== undefined &&
  import.meta.url === `file://${process.argv[1]}`;

if (isDirectRun) {
  createProgram().parseAsync(process.argv).catch((err: unknown) => {
    if (err instanceof ApiError) {
      if (err.status === 401) {
        console.error("Error: you need to log in. Run 'mcpctl login' to authenticate.");
      } else if (err.status === 403) {
        console.error('Error: permission denied. You do not have access to this resource.');
      } else {
        let msg: string;
        try {
          const parsed = JSON.parse(err.body) as { error?: string; message?: string; details?: unknown };
          msg = parsed.error ?? parsed.message ?? err.body;
          if (parsed.details && Array.isArray(parsed.details)) {
            const issues = parsed.details as Array<{ message?: string; path?: string[] }>;
            const detail = issues.map((i) => {
              const path = i.path?.join('.') ?? '';
              return path ? `${path}: ${i.message}` : (i.message ?? '');
            }).filter(Boolean).join('; ');
            if (detail) msg += `: ${detail}`;
          }
        } catch {
          msg = err.body;
        }
        console.error(`Error: ${msg}`);
      }
    } else if (err instanceof Error) {
      console.error(`Error: ${err.message}`);
    } else {
      console.error(`Error: ${String(err)}`);
    }
    process.exit(1);
  });
}
