import { Command } from 'commander';
import { formatTable } from '../formatters/table.js';
import { formatJson, formatYamlMultiDoc } from '../formatters/output.js';
import type { Column } from '../formatters/table.js';
import { resolveResource, stripInternalFields } from './shared.js';

export interface GetCommandDeps {
  fetchResource: (resource: string, id?: string, opts?: { project?: string; all?: boolean }) => Promise<unknown[]>;
  log: (...args: string[]) => void;
  getProject?: () => string | undefined;
  mcplocalUrl?: string;
}

interface ServerRow {
  id: string;
  name: string;
  transport: string;
  packageName: string | null;
  dockerImage: string | null;
}

interface ProjectRow {
  id: string;
  name: string;
  description: string;
  proxyModel: string;
  gated?: boolean;
  ownerId: string;
  servers?: Array<{ server: { name: string } }>;
}

interface SecretRow {
  id: string;
  name: string;
  data: Record<string, string>;
}

interface TemplateRow {
  id: string;
  name: string;
  version: string;
  transport: string;
  packageName: string | null;
  description: string;
}

interface InstanceRow {
  id: string;
  serverId: string;
  server?: { name: string };
  status: string;
  containerId: string | null;
  port: number | null;
  healthStatus: string | null;
}

const serverColumns: Column<ServerRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'TRANSPORT', key: 'transport', width: 16 },
  { header: 'PACKAGE', key: (r) => r.packageName ?? '-' },
  { header: 'IMAGE', key: (r) => r.dockerImage ?? '-' },
  { header: 'ID', key: 'id' },
];

interface UserRow {
  id: string;
  email: string;
  name: string | null;
  provider: string | null;
}

interface GroupRow {
  id: string;
  name: string;
  description: string;
  members?: Array<{ user: { email: string } }>;
}

interface RbacRow {
  id: string;
  name: string;
  subjects: Array<{ kind: string; name: string }>;
  roleBindings: Array<{ role: string; resource?: string; action?: string; name?: string }>;
}

const projectColumns: Column<ProjectRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'PLUGIN', key: (r) => r.proxyModel || 'default', width: 18 },
  { header: 'SERVERS', key: (r) => r.servers ? String(r.servers.length) : '0', width: 8 },
  { header: 'DESCRIPTION', key: 'description', width: 30 },
  { header: 'ID', key: 'id' },
];

const userColumns: Column<UserRow>[] = [
  { header: 'EMAIL', key: 'email' },
  { header: 'NAME', key: (r) => r.name ?? '-' },
  { header: 'PROVIDER', key: (r) => r.provider ?? 'local', width: 10 },
  { header: 'ID', key: 'id' },
];

const groupColumns: Column<GroupRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'MEMBERS', key: (r) => r.members ? String(r.members.length) : '0', width: 8 },
  { header: 'DESCRIPTION', key: 'description', width: 40 },
  { header: 'ID', key: 'id' },
];

const rbacColumns: Column<RbacRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'SUBJECTS', key: (r) => r.subjects.map((s) => `${s.kind}:${s.name}`).join(', '), width: 30 },
  { header: 'BINDINGS', key: (r) => r.roleBindings.map((b) => {
    if ('action' in b && b.action !== undefined) return `run>${b.action}`;
    if ('resource' in b && b.resource !== undefined) {
      const base = `${b.role}:${b.resource}`;
      return b.name ? `${base}:${b.name}` : base;
    }
    return b.role;
  }).join(', '), width: 40 },
  { header: 'ID', key: 'id' },
];

const secretColumns: Column<SecretRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'KEYS', key: (r) => Object.keys(r.data).join(', ') || '-', width: 40 },
  { header: 'ID', key: 'id' },
];

const templateColumns: Column<TemplateRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'VERSION', key: 'version', width: 10 },
  { header: 'TRANSPORT', key: 'transport', width: 16 },
  { header: 'PACKAGE', key: (r) => r.packageName ?? '-' },
  { header: 'DESCRIPTION', key: 'description', width: 50 },
];

interface PromptRow {
  id: string;
  name: string;
  projectId: string | null;
  project?: { name: string } | null;
  priority: number;
  linkTarget: string | null;
  linkStatus: 'alive' | 'dead' | null;
  createdAt: string;
}

interface PromptRequestRow {
  id: string;
  name: string;
  projectId: string | null;
  project?: { name: string } | null;
  createdBySession: string | null;
  createdAt: string;
}

const promptColumns: Column<PromptRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'PROJECT', key: (r) => r.project?.name ?? (r.projectId ? r.projectId : '(global)'), width: 20 },
  { header: 'PRI', key: (r) => String(r.priority), width: 4 },
  { header: 'LINK', key: (r) => r.linkTarget ? r.linkTarget.split(':')[0]! : '-', width: 20 },
  { header: 'STATUS', key: (r) => r.linkStatus ?? '-', width: 6 },
  { header: 'CREATED', key: (r) => new Date(r.createdAt).toLocaleString(), width: 20 },
  { header: 'ID', key: 'id' },
];

const promptRequestColumns: Column<PromptRequestRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'PROJECT', key: (r) => r.project?.name ?? (r.projectId ? r.projectId : '(global)'), width: 20 },
  { header: 'SESSION', key: (r) => r.createdBySession ? r.createdBySession.slice(0, 12) : '-', width: 14 },
  { header: 'CREATED', key: (r) => new Date(r.createdAt).toLocaleString(), width: 20 },
  { header: 'ID', key: 'id' },
];

const instanceColumns: Column<InstanceRow>[] = [
  { header: 'NAME', key: (r) => r.server?.name ?? '-', width: 20 },
  { header: 'STATUS', key: 'status', width: 10 },
  { header: 'HEALTH', key: (r) => r.healthStatus ?? '-', width: 10 },
  { header: 'PORT', key: (r) => r.port != null ? String(r.port) : '-', width: 6 },
  { header: 'CONTAINER', key: (r) => r.containerId ? r.containerId.slice(0, 12) : '-', width: 14 },
  { header: 'ID', key: 'id' },
];

interface ServerAttachmentRow {
  project: string;
  server: string;
}

const serverAttachmentColumns: Column<ServerAttachmentRow>[] = [
  { header: 'SERVER', key: 'server', width: 25 },
  { header: 'PROJECT', key: 'project', width: 25 },
];

interface ProxymodelRow {
  name: string;
  source: string;
  type?: string;
  controller?: string;
  stages?: string[];
  cacheable?: boolean;
  extends?: readonly string[];
  hooks?: string[];
  description?: string;
}

const proxymodelColumns: Column<ProxymodelRow>[] = [
  { header: 'NAME', key: 'name' },
  { header: 'TYPE', key: (r) => r.type ?? 'pipeline', width: 10 },
  { header: 'SOURCE', key: 'source', width: 10 },
  { header: 'DETAIL', key: (r) => {
    if (r.type === 'plugin') {
      const ext = r.extends?.length ? `extends: ${[...r.extends].join(', ')}` : '';
      const hooks = r.hooks?.length ? `hooks: ${r.hooks.length}` : '';
      return [ext, hooks].filter(Boolean).join(' | ') || '-';
    }
    return r.stages?.join(', ') ?? '-';
  }, width: 45 },
];

function getColumnsForResource(resource: string): Column<Record<string, unknown>>[] {
  switch (resource) {
    case 'servers':
      return serverColumns as unknown as Column<Record<string, unknown>>[];
    case 'projects':
      return projectColumns as unknown as Column<Record<string, unknown>>[];
    case 'secrets':
      return secretColumns as unknown as Column<Record<string, unknown>>[];
    case 'templates':
      return templateColumns as unknown as Column<Record<string, unknown>>[];
    case 'instances':
      return instanceColumns as unknown as Column<Record<string, unknown>>[];
    case 'users':
      return userColumns as unknown as Column<Record<string, unknown>>[];
    case 'groups':
      return groupColumns as unknown as Column<Record<string, unknown>>[];
    case 'rbac':
      return rbacColumns as unknown as Column<Record<string, unknown>>[];
    case 'prompts':
      return promptColumns as unknown as Column<Record<string, unknown>>[];
    case 'promptrequests':
      return promptRequestColumns as unknown as Column<Record<string, unknown>>[];
    case 'serverattachments':
      return serverAttachmentColumns as unknown as Column<Record<string, unknown>>[];
    case 'proxymodels':
      return proxymodelColumns as unknown as Column<Record<string, unknown>>[];
    default:
      return [
        { header: 'ID', key: 'id' as keyof Record<string, unknown> },
        { header: 'NAME', key: 'name' as keyof Record<string, unknown> },
      ];
  }
}

/** Map plural resource name → singular kind for YAML documents */
const RESOURCE_KIND: Record<string, string> = {
  servers: 'server',
  projects: 'project',
  secrets: 'secret',
  templates: 'template',
  instances: 'instance',
  users: 'user',
  groups: 'group',
  rbac: 'rbac',
  prompts: 'prompt',
  promptrequests: 'promptrequest',
  serverattachments: 'serverattachment',
};

/**
 * Transform API response items into apply-compatible multi-doc format.
 * Each item gets a `kind` field and internal fields stripped.
 */
function toApplyDocs(resource: string, items: unknown[]): Array<{ kind: string } & Record<string, unknown>> {
  const kind = RESOURCE_KIND[resource] ?? resource;
  return items.map((item) => {
    const cleaned = stripInternalFields(item as Record<string, unknown>);
    return { kind, ...cleaned };
  });
}

export function createGetCommand(deps: GetCommandDeps): Command {
  return new Command('get')
    .description('List resources (servers, projects, instances, all)')
    .argument('<resource>', 'resource type (servers, projects, instances, all)')
    .argument('[id]', 'specific resource ID or name')
    .option('-o, --output <format>', 'output format (table, json, yaml)', 'table')
    .option('-p, --project <name>', 'Filter by project')
    .option('-A, --all', 'Show all (including project-scoped) resources')
    .action(async (resourceArg: string, id: string | undefined, opts: { output: string; project?: string; all?: true }) => {
      const resource = resolveResource(resourceArg);
      // Merge parent --project with local --project
      const project = opts.project ?? deps.getProject?.();

      // Handle `get all --project X` composite export
      if (resource === 'all') {
        await handleGetAll(deps, { ...opts, project });
        return;
      }

      // ProxyModels are served by mcplocal, not mcpd
      if (resource === 'proxymodels') {
        const mcplocalUrl = deps.mcplocalUrl ?? 'http://localhost:3200';
        const items = await fetchProxymodels(mcplocalUrl, id);
        if (opts.output === 'json') {
          deps.log(formatJson(items));
        } else if (opts.output === 'yaml') {
          deps.log(formatYamlMultiDoc(items.map((i) => ({ kind: 'proxymodel', ...(i as Record<string, unknown>) }))));
        } else {
          if (items.length === 0) {
            deps.log('No proxymodels found.');
            return;
          }
          const columns = getColumnsForResource(resource);
          deps.log(formatTable(items as Record<string, unknown>[], columns));
        }
        return;
      }

      const fetchOpts: { project?: string; all?: boolean } = {};
      if (project) fetchOpts.project = project;
      if (opts.all) fetchOpts.all = true;
      const items = await deps.fetchResource(resource, id, Object.keys(fetchOpts).length > 0 ? fetchOpts : undefined);

      if (opts.output === 'json') {
        deps.log(formatJson(toApplyDocs(resource, items)));
      } else if (opts.output === 'yaml') {
        deps.log(formatYamlMultiDoc(toApplyDocs(resource, items)));
      } else {
        if (items.length === 0) {
          deps.log(`No ${resource} found.`);
          return;
        }
        const columns = getColumnsForResource(resource);
        deps.log(formatTable(items as Record<string, unknown>[], columns));
      }
    });
}

async function handleGetAll(
  deps: GetCommandDeps,
  opts: { output: string; project?: string },
): Promise<void> {
  if (!opts.project) {
    throw new Error('--project is required with "get all". Usage: mcpctl get all --project <name>');
  }

  const docs: Array<{ kind: string } & Record<string, unknown>> = [];

  // 1. Fetch the project
  const projects = await deps.fetchResource('projects', opts.project);
  if (projects.length === 0) {
    deps.log(`Project '${opts.project}' not found.`);
    return;
  }

  // 2. Add the project itself
  for (const p of projects) {
    docs.push({ kind: 'project', ...stripInternalFields(p as Record<string, unknown>) });
  }

  // 3. Extract serverattachments from project's server list
  const project = projects[0] as ProjectRow;
  let attachmentCount = 0;
  if (project.servers && project.servers.length > 0) {
    for (const ps of project.servers) {
      docs.push({
        kind: 'serverattachment',
        server: typeof ps === 'string' ? ps : ps.server.name,
        project: project.name,
      });
      attachmentCount++;
    }
  }

  // 4. Fetch prompts owned by this project (exclude global prompts)
  const prompts = await deps.fetchResource('prompts', undefined, { project: opts.project });
  const projectPrompts = prompts.filter((p) => (p as { projectId?: string }).projectId != null);
  for (const p of projectPrompts) {
    docs.push({ kind: 'prompt', ...stripInternalFields(p as Record<string, unknown>) });
  }

  if (opts.output === 'json') {
    deps.log(formatJson(docs));
  } else if (opts.output === 'yaml') {
    deps.log(formatYamlMultiDoc(docs));
  } else {
    // Table output: show summary
    deps.log(`Project: ${opts.project}`);
    deps.log(`  Server Attachments:   ${attachmentCount}`);
    deps.log(`  Prompts:              ${projectPrompts.length}`);
    deps.log(`\nUse -o yaml or -o json for apply-compatible output.`);
  }
}

async function fetchProxymodels(mcplocalUrl: string, name?: string): Promise<unknown[]> {
  const http = await import('node:http');
  const url = name
    ? `${mcplocalUrl}/proxymodels/${encodeURIComponent(name)}`
    : `${mcplocalUrl}/proxymodels`;

  return new Promise<unknown[]>((resolve, reject) => {
    const req = http.get(url, { timeout: 5000 }, (res) => {
      let data = '';
      res.on('data', (chunk: Buffer) => { data += chunk.toString(); });
      res.on('end', () => {
        try {
          const parsed = JSON.parse(data) as unknown;
          resolve(Array.isArray(parsed) ? parsed : [parsed]);
        } catch {
          reject(new Error('Invalid response from mcplocal'));
        }
      });
    });
    req.on('error', () => reject(new Error(`Cannot connect to mcplocal at ${mcplocalUrl}`)));
    req.on('timeout', () => { req.destroy(); reject(new Error('mcplocal request timed out')); });
  });
}
