import type Database from 'better-sqlite3';
import { createRegistry } from '../mcp/registry.js';
import type { McpServerPublic } from '../mcp/types.js';
import { logger } from '../logger.js';
import { createConnectionRepo, type SshConnection } from '../ssh/connection-repo.js';
import { decryptPrivateKey, encryptPrivateKey } from '../ssh/crypto.js';

export interface TransferDeps {
  db: Database.Database;
}

export interface TransferCandidate {
  type: 'mcp' | 'ssh';
  id: string;
  name: string;
  detail: string;
  willSkip: boolean;
  skipReason?: string;
}

export interface TransferResult {
  copied: Array<{ type: 'mcp' | 'ssh'; id: string; name: string }>;
  skipped: Array<{ type: 'mcp' | 'ssh'; name: string; reason: string }>;
}

/** 移し先に同名 or 同 url の MCP があるか。 */
function mcpSkipReason(target: McpServerPublic[], src: McpServerPublic): string | null {
  if (target.some((t) => t.name === src.name)) return `同名のサーバが既にあります（${src.name}）`;
  if (target.some((t) => t.url === src.url)) return `同じ接続先が既にあります（${src.url}）`;
  return null;
}

export function listMcpCandidates(
  deps: TransferDeps,
  sourceSpaceId: string,
  targetSpaceId: string,
): TransferCandidate[] {
  const reg = createRegistry(deps.db);
  const target = reg.listForSpace(targetSpaceId);
  return reg.listForSpace(sourceSpaceId).map((s) => {
    const reason = mcpSkipReason(target, s);
    return {
      type: 'mcp' as const,
      id: s.id,
      name: s.name,
      detail: s.url,
      willSkip: reason !== null,
      skipReason: reason ?? undefined,
    };
  });
}

export function transferMcpServer(
  deps: TransferDeps,
  sourceId: string,
  sourceSpaceId: string,
  targetSpaceId: string,
  actorId: string,
): { copied?: { type: 'mcp'; id: string; name: string }; skipped?: { type: 'mcp'; name: string; reason: string } } {
  const reg = createRegistry(deps.db);
  // 元WSへの所属を「復号せずに」確認する（SSH 経路と対称）。listForSpace は
  // McpServerPublic（シークレット非返却）なので、他スペースの id では相手の
  // シークレットをメモリに展開しないまま skip できる（IDOR 対策の前段）。
  if (!reg.listForSpace(sourceSpaceId).some((s) => s.id === sourceId)) {
    // listPublic も McpServerPublic（シークレット非返却）。存在しない id か、
    // 他スペースの id かを復号せずに区別して正確な理由を返す（SSH 経路と対称）。
    const existsElsewhere = reg.listPublic().some((s) => s.id === sourceId);
    return {
      skipped: {
        type: 'mcp',
        name: sourceId,
        reason: existsElsewhere ? '指定元ワークスペースに属さない' : '見つかりません',
      },
    };
  }
  const src = reg.getDecrypted(sourceId);
  if (!src) return { skipped: { type: 'mcp', name: sourceId, reason: '見つかりません' } };
  const reason = mcpSkipReason(reg.listForSpace(targetSpaceId), src);
  if (reason) return { skipped: { type: 'mcp', name: src.name, reason } };

  const newId = `${src.name.toLowerCase().replace(/[^a-z0-9_-]/g, '-').slice(0, 40)}-${Date.now().toString(36)}`;
  reg.upsert({
    id: newId,
    name: src.name,
    url: src.url,
    authKind: src.authKind,
    ownerId: null, // スペース所有（owner 束縛しない）
    spaceId: targetSpaceId,
    oauthClientId: src.oauthClientId,
    oauthClientSecret: src.oauthClientSecret,
    oauthScopes: src.oauthScopes,
    staticToken: src.staticToken ?? undefined,
    authHeaderName: src.authHeaderName,
    enabled: src.enabled,
    createdBy: actorId,
  });
  logger.info(`[cred-transfer] mcp copied src=${sourceId} -> space=${targetSpaceId} id=${newId}`);
  return { copied: { type: 'mcp', id: newId, name: src.name } };
}

// ────────────────────────────────────────────────────────────────────────────
// SSH 経路（DEK ラウンドトリップ）
// ────────────────────────────────────────────────────────────────────────────

/** 移し先に同名 or 同接続先の SSH 接続があるか。 */
function sshSkipReason(target: SshConnection[], src: SshConnection): string | null {
  if (target.some((t) => t.label === src.label)) return `同名の接続が既にあります（${src.label}）`;
  if (target.some((t) => t.host === src.host && t.username === src.username && t.port === src.port))
    return `同じ接続先が既にあります（${src.username}@${src.host}:${src.port}）`;
  return null;
}

export function listSshCandidates(
  deps: TransferDeps,
  sourceSpaceId: string,
  targetSpaceId: string,
): TransferCandidate[] {
  const repo = createConnectionRepo(deps.db);
  const target = repo.listBySpace(targetSpaceId);
  return repo.listBySpace(sourceSpaceId).map((s) => {
    const reason = sshSkipReason(target, s);
    return {
      type: 'ssh' as const,
      id: s.id,
      name: s.label,
      detail: `${s.username}@${s.host}:${s.port}`,
      willSkip: reason !== null,
      skipReason: reason ?? undefined,
    };
  });
}

export function transferSshConnection(
  deps: TransferDeps,
  sourceId: string,
  sourceSpaceId: string,
  targetSpaceId: string,
  actorId: string,
): { copied?: { type: 'ssh'; id: string; name: string }; skipped?: { type: 'ssh'; name: string; reason: string } } {
  const repo = createConnectionRepo(deps.db);
  const src = repo.resolveConnection(sourceId);
  if (!src) return { skipped: { type: 'ssh', name: sourceId, reason: '見つかりません' } };
  if (src.spaceId !== sourceSpaceId) {
    return { skipped: { type: 'ssh', name: src.label, reason: '指定元ワークスペースに属さない' } };
  }
  const reason = sshSkipReason(repo.listBySpace(targetSpaceId), src);
  if (reason) return { skipped: { type: 'ssh', name: src.label, reason } };

  // 元WSのDEKで復号 → 移し先WSのDEKで再暗号化。平文は即クリア。
  const pem = decryptPrivateKey(deps.db, src.ownerId, src.privateKeyEnc, src.spaceId);
  let passphrase: Buffer | null = null;
  try {
    if (src.passphraseEnc) passphrase = decryptPrivateKey(deps.db, src.ownerId, src.passphraseEnc, src.spaceId);
    const pk = encryptPrivateKey(deps.db, null, pem, targetSpaceId);
    const pp = passphrase ? encryptPrivateKey(deps.db, null, passphrase, targetSpaceId) : null;
    const created = repo.create({
      ownerId: null, // スペース所有
      spaceId: targetSpaceId,
      label: src.label,
      host: src.host,
      port: src.port,
      username: src.username,
      privateKeyEnc: pk.blob,
      passphraseEnc: pp ? pp.blob : null,
      keyVersion: pk.keyVersion,
      keyFingerprint: src.keyFingerprint ?? 'SHA256:unknown',
      remotePathPrefix: src.remotePathPrefix,
      allowRemoteUnrestricted: src.allowRemoteUnrestricted,
      allowPrivateAddresses: src.allowPrivateAddresses,
      commandDenyPatterns: src.commandDenyPatterns,
      commandAllowPatterns: src.commandAllowPatterns,
    });
    logger.info(`[cred-transfer] ssh copied src=${sourceId} -> space=${targetSpaceId} id=${created.id}`);
    return { copied: { type: 'ssh', id: created.id, name: src.label } };
  } finally {
    pem.fill(0);
    passphrase?.fill(0);
  }
}

// ────────────────────────────────────────────────────────────────────────────
// オーケストレーション（MCP + SSH 統合）
// ────────────────────────────────────────────────────────────────────────────

/** スペース間転送の候補一覧を MCP・SSH それぞれ返す。 */
export function listTransferCandidates(
  deps: TransferDeps,
  sourceSpaceId: string,
  targetSpaceId: string,
): { mcp: TransferCandidate[]; ssh: TransferCandidate[] } {
  return {
    mcp: listMcpCandidates(deps, sourceSpaceId, targetSpaceId),
    ssh: listSshCandidates(deps, sourceSpaceId, targetSpaceId),
  };
}

/** 指定した MCP サーバー・SSH 接続を一括転送し、copied/skipped のサマリを返す。 */
export function executeTransfer(
  deps: TransferDeps,
  opts: {
    sourceSpaceId: string;
    targetSpaceId: string;
    actorId: string;
    mcpServerIds: string[];
    sshConnectionIds: string[];
  },
): TransferResult {
  return deps.db.transaction(() => {
    const result: TransferResult = { copied: [], skipped: [] };
    for (const id of opts.mcpServerIds) {
      const r = transferMcpServer(deps, id, opts.sourceSpaceId, opts.targetSpaceId, opts.actorId);
      if (r.copied) result.copied.push(r.copied);
      if (r.skipped) result.skipped.push(r.skipped);
    }
    for (const id of opts.sshConnectionIds) {
      const r = transferSshConnection(deps, id, opts.sourceSpaceId, opts.targetSpaceId, opts.actorId);
      if (r.copied) result.copied.push(r.copied);
      if (r.skipped) result.skipped.push(r.skipped);
    }
    return result;
  })();
}
