import { Command } from 'commander';
import { type ApiClient, ApiError } from '../api-client.js';
import { resolveNameOrId } from './shared.js';
export interface CreateCommandDeps {
  client: ApiClient;
  log: (...args: unknown[]) => void;
}

function collect(value: string, prev: string[]): string[] {
  return [...prev, value];
}

interface ServerEnvEntry {
  name: string;
  value?: string;
  valueFrom?: { secretRef: { name: string; key: string } };
}

function parseServerEnv(entries: string[]): ServerEnvEntry[] {
  return entries.map((entry) => {
    const eqIdx = entry.indexOf('=');
    if (eqIdx === -1) {
      throw new Error(`Invalid env format '${entry}'. Expected KEY=value or KEY=secretRef:SECRET:KEY`);
    }
    const envName = entry.slice(0, eqIdx);
    const rhs = entry.slice(eqIdx + 1);

    if (rhs.startsWith('secretRef:')) {
      const parts = rhs.split(':');
      if (parts.length !== 3) {
        throw new Error(`Invalid secretRef format '${entry}'. Expected KEY=secretRef:SECRET_NAME:SECRET_KEY`);
      }
      return {
        name: envName,
        valueFrom: { secretRef: { name: parts[1]!, key: parts[2]! } },
      };
    }

    return { name: envName, value: rhs };
  });
}

function parseEnvEntries(entries: string[]): Record<string, string> {
  const result: Record<string, string> = {};
  for (const entry of entries) {
    const eqIdx = entry.indexOf('=');
    if (eqIdx === -1) {
      throw new Error(`Invalid env format '${entry}'. Expected KEY=value`);
    }
    result[entry.slice(0, eqIdx)] = entry.slice(eqIdx + 1);
  }
  return result;
}

export function createCreateCommand(deps: CreateCommandDeps): Command {
  const { client, log } = deps;

  const cmd = new Command('create')
    .description('Create a resource (server, secret, project, user, group, rbac, serverattachment, prompt)');

  // --- create server ---
  cmd.command('server')
    .description('Create an MCP server definition')
    .argument('<name>', 'Server name (lowercase, hyphens allowed)')
    .option('-d, --description <text>', 'Server description')
    .option('--package-name <name>', 'Package name (npm, PyPI, Go module, etc.)')
    .option('--runtime <type>', 'Package runtime (node, python, go — default: node)')
    .option('--docker-image <image>', 'Docker image')
    .option('--transport <type>', 'Transport type (STDIO, SSE, STREAMABLE_HTTP)')
    .option('--repository-url <url>', 'Source repository URL')
    .option('--external-url <url>', 'External endpoint URL')
    .option('--command <arg>', 'Command argument (repeat for multiple)', collect, [])
    .option('--container-port <port>', 'Container port number')
    .option('--replicas <count>', 'Number of replicas')
    .option('--env <entry>', 'Env var: KEY=value (inline) or KEY=secretRef:SECRET:KEY (secret ref, repeat for multiple)', collect, [])
    .option('--from-template <name>', 'Create from template (name or name:version)')
    .option('--env-from-secret <secret>', 'Map template env vars from a secret')
    .option('--force', 'Update if already exists')
    .action(async (name: string, opts) => {
      let base: Record<string, unknown> = {};

      // If --from-template, fetch template and use as base
      if (opts.fromTemplate) {
        const tplRef = opts.fromTemplate as string;
        const [tplName, tplVersion] = tplRef.includes(':')
          ? [tplRef.slice(0, tplRef.indexOf(':')), tplRef.slice(tplRef.indexOf(':') + 1)]
          : [tplRef, undefined];

        const templates = await client.get<Array<Record<string, unknown>>>(`/api/v1/templates?name=${encodeURIComponent(tplName)}`);
        let template: Record<string, unknown> | undefined;
        if (tplVersion) {
          template = templates.find((t) => t.name === tplName && t.version === tplVersion);
          if (!template) throw new Error(`Template '${tplName}' version '${tplVersion}' not found`);
        } else {
          template = templates.find((t) => t.name === tplName);
          if (!template) throw new Error(`Template '${tplName}' not found`);
        }

        // Copy template fields as base (strip template-only, internal, and null fields)
        const { id: _id, createdAt: _c, updatedAt: _u, version: _v, name: _n, ...tplFields } = template;
        base = {};
        for (const [k, v] of Object.entries(tplFields)) {
          if (v !== null && v !== undefined) base[k] = v;
        }

        // Convert template env (description/required) to server env (name/value/valueFrom)
        const tplEnv = template.env as Array<{ name: string; description?: string; required?: boolean; defaultValue?: string }> | undefined;
        if (tplEnv && tplEnv.length > 0) {
          if (opts.envFromSecret) {
            // --env-from-secret: map all template env vars from the specified secret
            const secretName = opts.envFromSecret as string;
            const secrets = await client.get<Array<{ name: string; data: Record<string, string> }>>('/api/v1/secrets');
            const secret = secrets.find((s) => s.name === secretName);
            if (!secret) throw new Error(`Secret '${secretName}' not found`);

            const missing = tplEnv
              .filter((e) => e.required !== false && !(e.name in secret.data))
              .map((e) => e.name);
            if (missing.length > 0) {
              throw new Error(
                `Secret '${secretName}' is missing required keys: ${missing.join(', ')}\n` +
                `Secret has: ${Object.keys(secret.data).join(', ')}`,
              );
            }

            base.env = tplEnv.map((e) => {
              if (e.name in secret.data) {
                return { name: e.name, valueFrom: { secretRef: { name: secretName, key: e.name } } };
              }
              return { name: e.name, value: e.defaultValue ?? '' };
            });
            log(`Mapped ${tplEnv.filter((e) => e.name in secret.data).length} env var(s) from secret '${secretName}'`);
          } else {
            base.env = tplEnv.map((e) => ({ name: e.name, value: e.defaultValue ?? '' }));
          }
        }

        // Track template origin
        base.templateName = tplName;
        base.templateVersion = (template.version as string) ?? '1.0.0';
      }

      // Build body: template base → CLI overrides (last wins)
      const body: Record<string, unknown> = {
        ...base,
        name,
      };
      if (opts.description !== undefined) body.description = opts.description;
      if (opts.transport) body.transport = opts.transport;
      if (opts.replicas) body.replicas = parseInt(opts.replicas, 10);
      if (opts.packageName) body.packageName = opts.packageName;
      if (opts.runtime) body.runtime = opts.runtime;
      if (opts.dockerImage) body.dockerImage = opts.dockerImage;
      if (opts.repositoryUrl) body.repositoryUrl = opts.repositoryUrl;
      if (opts.externalUrl) body.externalUrl = opts.externalUrl;
      if (opts.command.length > 0) body.command = opts.command;
      if (opts.containerPort) body.containerPort = parseInt(opts.containerPort, 10);
      if (opts.env.length > 0) {
        // Merge: CLI env entries override template env entries by name
        const cliEnv = parseServerEnv(opts.env);
        const existing = (body.env as ServerEnvEntry[] | undefined) ?? [];
        const merged = [...existing];
        for (const entry of cliEnv) {
          const idx = merged.findIndex((e) => e.name === entry.name);
          if (idx >= 0) {
            merged[idx] = entry;
          } else {
            merged.push(entry);
          }
        }
        body.env = merged;
      }

      // Defaults when no template
      if (!opts.fromTemplate) {
        if (body.description === undefined) body.description = '';
        if (!body.transport) body.transport = 'STDIO';
        if (!body.replicas) body.replicas = 1;
      }

      try {
        const server = await client.post<{ id: string; name: string }>('/api/v1/servers', body);
        log(`server '${server.name}' created (id: ${server.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; name: string }>>('/api/v1/servers')).find((s) => s.name === name);
          if (!existing) throw err;
          const { name: _n, ...updateBody } = body;
          await client.put(`/api/v1/servers/${existing.id}`, updateBody);
          log(`server '${name}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create secret ---
  cmd.command('secret')
    .description('Create a secret')
    .argument('<name>', 'Secret name (lowercase, hyphens allowed)')
    .option('--data <entry>', 'Secret data KEY=value (repeat for multiple)', collect, [])
    .option('--force', 'Update if already exists')
    .action(async (name: string, opts) => {
      const data = parseEnvEntries(opts.data);
      try {
        const secret = await client.post<{ id: string; name: string }>('/api/v1/secrets', {
          name,
          data,
        });
        log(`secret '${secret.name}' created (id: ${secret.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; name: string }>>('/api/v1/secrets')).find((s) => s.name === name);
          if (!existing) throw err;
          await client.put(`/api/v1/secrets/${existing.id}`, { data });
          log(`secret '${name}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create project ---
  cmd.command('project')
    .description('Create a project')
    .argument('<name>', 'Project name')
    .option('-d, --description <text>', 'Project description', '')
    .option('--proxy-model <name>', 'Plugin name (default, content-pipeline, gate, none)')
    .option('--prompt <text>', 'Project-level prompt / instructions for the LLM')
    .option('--gated', '[deprecated: use --proxy-model default]')
    .option('--no-gated', '[deprecated: use --proxy-model content-pipeline]')
    .option('--server <name>', 'Server name (repeat for multiple)', collect, [])
    .option('--force', 'Update if already exists')
    .action(async (name: string, opts) => {
      const body: Record<string, unknown> = {
        name,
        description: opts.description,
      };
      if (opts.prompt) body.prompt = opts.prompt;
      if (opts.proxyModel) {
        body.proxyModel = opts.proxyModel;
      } else if (opts.gated === false) {
        // Backward compat: --no-gated → proxyModel: content-pipeline
        body.proxyModel = 'content-pipeline';
      }
      // Pass gated for backward compat with older mcpd
      if (opts.gated !== undefined) body.gated = opts.gated as boolean;
      if (opts.server.length > 0) body.servers = opts.server;

      try {
        const project = await client.post<{ id: string; name: string }>('/api/v1/projects', body);
        log(`project '${project.name}' created (id: ${project.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; name: string }>>('/api/v1/projects')).find((p) => p.name === name);
          if (!existing) throw err;
          const { name: _n, ...updateBody } = body;
          await client.put(`/api/v1/projects/${existing.id}`, updateBody);
          log(`project '${name}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create user ---
  cmd.command('user')
    .description('Create a user')
    .argument('<email>', 'User email address')
    .option('--password <pass>', 'User password')
    .option('--name <name>', 'User display name')
    .option('--force', 'Update if already exists')
    .action(async (email: string, opts) => {
      if (!opts.password) {
        throw new Error('--password is required');
      }
      const body: Record<string, unknown> = {
        email,
        password: opts.password,
      };
      if (opts.name) body.name = opts.name;

      try {
        const user = await client.post<{ id: string; email: string }>('/api/v1/users', body);
        log(`user '${user.email}' created (id: ${user.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; email: string }>>('/api/v1/users')).find((u) => u.email === email);
          if (!existing) throw err;
          const { email: _e, ...updateBody } = body;
          await client.put(`/api/v1/users/${existing.id}`, updateBody);
          log(`user '${email}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create group ---
  cmd.command('group')
    .description('Create a group')
    .argument('<name>', 'Group name')
    .option('--description <text>', 'Group description')
    .option('--member <email>', 'Member email (repeat for multiple)', collect, [])
    .option('--force', 'Update if already exists')
    .action(async (name: string, opts) => {
      const body: Record<string, unknown> = {
        name,
        members: opts.member,
      };
      if (opts.description) body.description = opts.description;

      try {
        const group = await client.post<{ id: string; name: string }>('/api/v1/groups', body);
        log(`group '${group.name}' created (id: ${group.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; name: string }>>('/api/v1/groups')).find((g) => g.name === name);
          if (!existing) throw err;
          const { name: _n, ...updateBody } = body;
          await client.put(`/api/v1/groups/${existing.id}`, updateBody);
          log(`group '${name}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create rbac ---
  cmd.command('rbac')
    .description('Create an RBAC binding definition')
    .argument('<name>', 'RBAC binding name')
    .option('--subject <entry>', 'Subject as Kind:name (repeat for multiple)', collect, [])
    .option('--binding <entry>', 'Role binding as role:resource (e.g. edit:servers, run:projects)', collect, [])
    .option('--operation <action>', 'Operation binding (e.g. logs, backup)', collect, [])
    .option('--force', 'Update if already exists')
    .action(async (name: string, opts) => {
      const subjects = (opts.subject as string[]).map((entry: string) => {
        const colonIdx = entry.indexOf(':');
        if (colonIdx === -1) {
          throw new Error(`Invalid subject format '${entry}'. Expected Kind:name (e.g. User:alice@example.com)`);
        }
        return { kind: entry.slice(0, colonIdx), name: entry.slice(colonIdx + 1) };
      });

      const roleBindings: Array<Record<string, string>> = [];

      // Resource bindings from --binding flag (role:resource or role:resource:name)
      for (const entry of opts.binding as string[]) {
        const parts = entry.split(':');
        if (parts.length === 2) {
          roleBindings.push({ role: parts[0]!, resource: parts[1]! });
        } else if (parts.length === 3) {
          roleBindings.push({ role: parts[0]!, resource: parts[1]!, name: parts[2]! });
        } else {
          throw new Error(`Invalid binding format '${entry}'. Expected role:resource or role:resource:name (e.g. edit:servers, view:servers:my-ha)`);
        }
      }

      // Operation bindings from --operation flag
      for (const action of opts.operation as string[]) {
        roleBindings.push({ role: 'run', action });
      }

      const body: Record<string, unknown> = {
        name,
        subjects,
        roleBindings,
      };

      try {
        const rbac = await client.post<{ id: string; name: string }>('/api/v1/rbac', body);
        log(`rbac '${rbac.name}' created (id: ${rbac.id})`);
      } catch (err) {
        if (err instanceof ApiError && err.status === 409 && opts.force) {
          const existing = (await client.get<Array<{ id: string; name: string }>>('/api/v1/rbac')).find((r) => r.name === name);
          if (!existing) throw err;
          const { name: _n, ...updateBody } = body;
          await client.put(`/api/v1/rbac/${existing.id}`, updateBody);
          log(`rbac '${name}' updated (id: ${existing.id})`);
        } else {
          throw err;
        }
      }
    });

  // --- create prompt ---
  cmd.command('prompt')
    .description('Create an approved prompt')
    .argument('<name>', 'Prompt name (lowercase alphanumeric with hyphens)')
    .option('-p, --project <name>', 'Project name to scope the prompt to')
    .option('--content <text>', 'Prompt content text')
    .option('--content-file <path>', 'Read prompt content from file')
    .option('--priority <number>', 'Priority 1-10 (default: 5, higher = more important)')
    .option('--link <target>', 'Link to MCP resource (format: project/server:uri)')
    .action(async (name: string, opts) => {
      let content = opts.content as string | undefined;
      if (opts.contentFile) {
        const fs = await import('node:fs/promises');
        content = await fs.readFile(opts.contentFile as string, 'utf-8');
      }
      // For linked prompts, auto-generate placeholder content if none provided
      if (!content && opts.link) {
        content = `Linked prompt — content fetched from ${opts.link as string}`;
      }
      if (!content) {
        throw new Error('--content or --content-file is required');
      }

      const body: Record<string, unknown> = { name, content };
      if (opts.project) {
        // Resolve project name to ID
        const projects = await client.get<Array<{ id: string; name: string }>>('/api/v1/projects');
        const project = projects.find((p) => p.name === opts.project);
        if (!project) throw new Error(`Project '${opts.project as string}' not found`);
        body.projectId = project.id;
      }
      if (opts.priority) {
        const priority = Number(opts.priority);
        if (isNaN(priority) || priority < 1 || priority > 10) {
          throw new Error('--priority must be a number between 1 and 10');
        }
        body.priority = priority;
      }
      if (opts.link) {
        body.linkTarget = opts.link;
      }

      const prompt = await client.post<{ id: string; name: string }>('/api/v1/prompts', body);
      log(`prompt '${prompt.name}' created (id: ${prompt.id})`);
    });

  // --- create serverattachment ---
  cmd.command('serverattachment')
    .alias('sa')
    .description('Attach a server to a project')
    .argument('<server>', 'Server name')
    .option('-p, --project <name>', 'Project name')
    .action(async (serverName: string, opts) => {
      const projectName = opts.project as string | undefined;
      if (!projectName) {
        throw new Error('--project is required. Usage: mcpctl create serverattachment <server> --project <name>');
      }
      const projectId = await resolveNameOrId(client, 'projects', projectName);
      await client.post(`/api/v1/projects/${projectId}/servers`, { server: serverName });
      log(`server '${serverName}' attached to project '${projectName}'`);
    });

  // --- create promptrequest ---
  cmd.command('promptrequest')
    .description('Create a prompt request (pending proposal that needs approval)')
    .argument('<name>', 'Prompt request name (lowercase alphanumeric with hyphens)')
    .option('-p, --project <name>', 'Project name to scope the prompt request to')
    .option('--content <text>', 'Prompt content text')
    .option('--content-file <path>', 'Read prompt content from file')
    .option('--priority <number>', 'Priority 1-10 (default: 5, higher = more important)')
    .action(async (name: string, opts) => {
      let content = opts.content as string | undefined;
      if (opts.contentFile) {
        const fs = await import('node:fs/promises');
        content = await fs.readFile(opts.contentFile as string, 'utf-8');
      }
      if (!content) {
        throw new Error('--content or --content-file is required');
      }

      const body: Record<string, unknown> = { name, content };
      if (opts.project) {
        body.project = opts.project;
      }
      if (opts.priority) {
        const priority = Number(opts.priority);
        if (isNaN(priority) || priority < 1 || priority > 10) {
          throw new Error('--priority must be a number between 1 and 10');
        }
        body.priority = priority;
      }

      const pr = await client.post<{ id: string; name: string }>(
        '/api/v1/promptrequests',
        body,
      );
      log(`prompt request '${pr.name}' created (id: ${pr.id})`);
      log(`  approve with: mcpctl approve promptrequest ${pr.name}`);
    });

  return cmd;
}
