#!/usr/bin/env node

/**
 * OpenMonetis Setup Script
 * Uso: node setup.mjs
 */

import { createInterface } from "readline";
import { execSync } from "child_process";
import { writeFileSync, existsSync } from "fs";
import { randomBytes } from "crypto";
import { resolve, join } from "path";

// ─── Cores e símbolos ────────────────────────────────────────────────────────

const c = {
  reset: "\x1b[0m",
  bold: "\x1b[1m",
  dim: "\x1b[2m",
  green: "\x1b[32m",
  red: "\x1b[31m",
  yellow: "\x1b[33m",
  cyan: "\x1b[36m",
  orange: "\x1b[38;5;214m",
};

const sym = {
  ok: `${c.green}✔${c.reset}`,
  fail: `${c.red}✗${c.reset}`,
  warn: `${c.yellow}!${c.reset}`,
  arrow: `${c.cyan}→${c.reset}`,
};

// ─── Helpers ─────────────────────────────────────────────────────────────────

function section(label) {
  console.log(`\n${c.dim}── ${label} ${"─".repeat(Math.max(0, 48 - label.length))}${c.reset}`);
}

function runSilent(cmd) {
  try {
    return execSync(cmd, { stdio: "pipe" }).toString().trim();
  } catch {
    return null;
  }
}

function run(cmd, opts = {}) {
  execSync(cmd, { stdio: "pipe", ...opts });
}

function spinner(text) {
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
  let i = 0;
  const id = setInterval(() => {
    process.stdout.write(`\r${c.cyan}${frames[i++ % frames.length]}${c.reset} ${text}`);
  }, 80);
  return {
    stop: (msg) => { clearInterval(id); process.stdout.write(`\r${sym.ok} ${msg}\n`); },
    fail: (msg) => { clearInterval(id); process.stdout.write(`\r${sym.fail} ${msg}\n`); },
  };
}

const rl = createInterface({ input: process.stdin, output: process.stdout });
const ask = (q) => new Promise((res) => rl.question(q, res));

async function askDefault(question, defaultValue) {
  const answer = await ask(`${question} [${c.dim}${defaultValue}${c.reset}]: `);
  return answer.trim() || defaultValue;
}

async function askYesNo(question) {
  const answer = await ask(`${question} ${c.dim}[s/N]${c.reset}: `);
  return answer.trim().toLowerCase() === "s";
}

function abort(msg) {
  console.log(`\n${sym.fail} ${msg}\n`);
  rl.close();
  process.exit(1);
}

// ─── Header ──────────────────────────────────────────────────────────────────

const logoLines = [
  ".............................+@@@@@@@@@@=.............................",
  ".............................@@@@@@@@@@@:.............................",
  "...................+@@@@@@*-:@@@@@@@@@@%...=@@@@@@-...................",
  "..................@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%..................",
  "................=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+................",
  "...................-=+%@@@@@@@@@@@@@@@@@@@@@*:........................",
  ".......................#@@@@@@@@@@@@@@@@@@@@@@@+......................",
  "....................%@@@@@@@@@@@@@%#@@@@@@@@@@@@*.....................",
  "....................+@@@@@@@@@@@......*@@@@@@#........................",
  ".........................:#@@=...........+#...........................",
];

const nameLines = [
  "   ___                   __  __                  _   _     ",
  "  / _ \\ _ __   ___ _ __ |  \\/  | ___  _ __   ___| |_(_)___ ",
  " | | | | '_ \\ / _ \\ '_ \\| |\\/| |/ _ \\| '_ \\ / _ \\ __| / __|",
  " | |_| | |_) |  __/ | | | |  | | (_) | | | |  __/ |_| \\__ \\",
  "  \\___/| .__/ \\___|_| |_|_|  |_|\\___/|_| |_|\\___|\\__|_|___/",
  "       |_|                                                   ",
];

const nameStart = Math.floor((logoLines.length - nameLines.length) / 2);

console.log();
for (let i = 0; i < logoLines.length; i++) {
  const logoCol = c.orange + logoLines[i].replaceAll(".", " ").substring(14, 56).padEnd(42) + c.reset;
  const nameIdx = i - nameStart;
  const nameCol = nameIdx >= 0 && nameIdx < nameLines.length ? nameLines[nameIdx] : "";
  console.log(logoCol + "  " + nameCol);
}
console.log(`\n${" ".repeat(46)}${c.dim}Gestão financeira · self-hosted${c.reset}\n`);

// ─── ETAPA 1: Verificações do sistema ────────────────────────────────────────

section("Verificando sistema");

// Node
const nodeMajor = parseInt(process.versions.node.split(".")[0]);
if (nodeMajor < 22) {
  console.log(`${sym.fail} Node.js ${process.versions.node} — requer 22+`);
  console.log(`   ${sym.arrow} https://nodejs.org`);
  process.exit(1);
}
console.log(`${sym.ok} Node.js ${process.versions.node}`);

// pnpm
let pnpmVersion = runSilent("pnpm --version");
if (!pnpmVersion) {
  process.stdout.write(`${sym.warn} pnpm não encontrado — instalando... `);
  try {
    run("npm install -g pnpm");
    pnpmVersion = runSilent("pnpm --version");
    process.stdout.write(`${sym.ok}\n`);
    console.log(`${sym.ok} pnpm ${pnpmVersion}`);
  } catch {
    console.log(`\n${sym.fail} Falha ao instalar pnpm`);
    console.log(`   ${sym.arrow} npm install -g pnpm`);
    process.exit(1);
  }
} else {
  console.log(`${sym.ok} pnpm ${pnpmVersion}`);
}

// Git
if (!runSilent("git --version")) {
  console.log(`${sym.fail} Git não encontrado`);
  console.log(`   ${sym.arrow} https://git-scm.com`);
  process.exit(1);
}
console.log(`${sym.ok} Git disponível`);

// Docker
const dockerAvailable = !!runSilent("docker --version");
if (dockerAvailable) {
  console.log(`${sym.ok} Docker disponível`);
} else {
  console.log(`${sym.warn} Docker não encontrado — banco local indisponível`);
}

// ─── ETAPA 2: Banco de dados ──────────────────────────────────────────────────

section("Banco de dados");

let databaseUrl;
let useLocalDocker = false;

if (dockerAvailable) {
  console.log(`  [1] PostgreSQL local via Docker  ${c.dim}(recomendado)${c.reset}`);
  console.log(`  [2] URL remota  ${c.dim}(Supabase, Neon, Railway...)${c.reset}\n`);
  const dbChoice = await ask(`Escolha [1]: `);

  if (dbChoice.trim() === "2") {
    databaseUrl = await ask(`DATABASE_URL: `);
    if (!databaseUrl.match(/^postgre(s|sql):\/\//)) {
      abort("URL inválida — deve começar com postgresql:// ou postgres://");
    }
  } else {
    useLocalDocker = true;
    databaseUrl =
      "postgresql://openmonetis:openmonetis_dev_password@localhost:5432/openmonetis_db";
    console.log(`${sym.ok} Banco local selecionado`);
  }
} else {
  console.log(`  ${c.dim}Insira a URL de um banco remoto (Supabase, Neon, Railway...)${c.reset}\n`);
  databaseUrl = await ask(`DATABASE_URL: `);
  if (!databaseUrl.match(/^postgre(s|sql):\/\//)) {
    abort("URL inválida — deve começar com postgresql:// ou postgres://");
  }
}

// ─── ETAPA 3: Autenticação ────────────────────────────────────────────────────

section("Autenticação");

const authSecret = randomBytes(32).toString("base64");
const betterAuthUrl = await askDefault("URL da aplicação", "http://localhost:3000");

console.log(`${sym.ok} BETTER_AUTH_SECRET gerado`);
console.log(`${sym.ok} BETTER_AUTH_URL: ${betterAuthUrl}`);

// ─── ETAPA 4: Opcionais ───────────────────────────────────────────────────────

section("Opcionais");
console.log(`  ${c.dim}Deixe em branco e configure depois editando o .env${c.reset}\n`);

// Google OAuth
let googleClientId = "";
let googleClientSecret = "";
if (await askYesNo("  Google OAuth (login social)?")) {
  googleClientId = await ask("  GOOGLE_CLIENT_ID: ");
  googleClientSecret = await ask("  GOOGLE_CLIENT_SECRET: ");
}

// Resend
let resendApiKey = "";
let resendFromEmail = "";
if (await askYesNo("  E-mail via Resend (notificações e convites)?")) {
  resendApiKey = await ask("  RESEND_API_KEY: ");
  resendFromEmail = await ask(`  RESEND_FROM_EMAIL [OpenMonetis <noreply@seudominio.com>]: `);
  if (!resendFromEmail.trim()) resendFromEmail = "OpenMonetis <noreply@seudominio.com>";
}

// AI
let anthropicKey = "";
let openaiKey = "";
let googleAiKey = "";
let minimaxKey = "";
let openrouterKey = "";
let ollamaBaseUrl = "";
let ollamaApiKey = "";
if (await askYesNo("  Insights com IA (Claude, GPT, Gemini, MiniMax, OpenRouter)?")) {
  console.log(`  ${c.dim}Deixe em branco o que não for usar${c.reset}`);
  anthropicKey = await ask("  ANTHROPIC_API_KEY: ");
  openaiKey = await ask("  OPENAI_API_KEY: ");
  googleAiKey = await ask("  GOOGLE_GENERATIVE_AI_API_KEY: ");
  minimaxKey = await ask("  MINIMAX_API_KEY: ");
  openrouterKey = await ask("  OPENROUTER_API_KEY: ");
}
if (await askYesNo("  Insights locais com Ollama?")) {
  ollamaBaseUrl = await askDefault("  OLLAMA_BASE_URL", "http://localhost:11434/v1");
  ollamaApiKey = await ask("  OLLAMA_API_KEY (opcional): ");
}

// Domínio público
let publicDomain = "";
if (await askYesNo("  Domínio público separado para a landing page?")) {
  publicDomain = await ask("  PUBLIC_DOMAIN (ex: openmonetis.com): ");
}

rl.close();

// ─── ETAPA 5: Confirmar e executar ────────────────────────────────────────────

const targetDir = resolve("openmonetis");

section("Instalação");
console.log(`
  ${sym.arrow} Clonar repositório em ./openmonetis
  ${sym.arrow} Gerar .env
  ${sym.arrow} pnpm install${useLocalDocker ? `\n  ${sym.arrow} Subir banco PostgreSQL (Docker)\n  ${sym.arrow} Habilitar extensões` : ""}
  ${sym.arrow} pnpm db:push
`);

if (existsSync(targetDir)) {
  abort("A pasta ./openmonetis já existe. Remova-a e tente novamente.");
}

// Clonar
let s = spinner("Clonando repositório...");
try {
  run("git clone https://github.com/felipegcoutinho/openmonetis.git openmonetis");
  s.stop("Repositório clonado");
} catch {
  s.fail("Falha ao clonar repositório");
  process.exit(1);
}

// Gerar .env
const val = (v, fallback = "") => v?.trim() || fallback;
const opt = (key, value) => (value?.trim() ? `${key}=${value}` : `# ${key}=`);

const envContent = [
  `# Gerado por setup.mjs em ${new Date().toISOString()}`,
  "",
  "# === Database ===",
  `DATABASE_URL=${databaseUrl}`,
  "",
  "# === Better Auth ===",
  `BETTER_AUTH_SECRET=${authSecret}`,
  `BETTER_AUTH_URL=${betterAuthUrl}`,
  "DISABLE_SIGNUP=false",
  "AUTH_SESSION_EXPIRES_IN_DAYS=30",
  "AUTH_SESSION_UPDATE_AGE_HOURS=24",
  "",
  "# === Portas ===",
  "APP_PORT=3000",
  "DB_PORT=5432",
  "",
  "# === PostgreSQL (Docker local) ===",
  "POSTGRES_USER=openmonetis",
  "POSTGRES_PASSWORD=openmonetis_dev_password",
  "POSTGRES_DB=openmonetis_db",
  "",
  "# === Multi-domínio ===",
  opt("PUBLIC_DOMAIN", publicDomain),
  "",
  "# === Google OAuth ===",
  opt("GOOGLE_CLIENT_ID", googleClientId),
  opt("GOOGLE_CLIENT_SECRET", googleClientSecret),
  "",
  "# === Email (Resend) ===",
  opt("RESEND_API_KEY", resendApiKey),
  resendFromEmail ? `RESEND_FROM_EMAIL="${resendFromEmail}"` : "# RESEND_FROM_EMAIL=",
  "",
  "# === AI Providers ===",
  opt("ANTHROPIC_API_KEY", anthropicKey),
  opt("OPENAI_API_KEY", openaiKey),
  opt("GOOGLE_GENERATIVE_AI_API_KEY", googleAiKey),
  opt("MINIMAX_API_KEY", minimaxKey),
  opt("OPENROUTER_API_KEY", openrouterKey),
  opt("OLLAMA_BASE_URL", ollamaBaseUrl),
  opt("OLLAMA_API_KEY", ollamaApiKey),
].join("\n");

writeFileSync(join(targetDir, ".env"), envContent);
console.log(`${sym.ok} .env gerado`);

// pnpm install
s = spinner("Instalando dependências...");
try {
  run("pnpm install", { cwd: targetDir });
  s.stop("Dependências instaladas");
} catch {
  s.fail("Falha ao instalar dependências");
  process.exit(1);
}

// Docker local
if (useLocalDocker) {
  s = spinner("Subindo banco PostgreSQL...");
  try {
    run("pnpm docker:up:db", { cwd: targetDir });
    s.stop("Banco iniciado");
  } catch {
    s.fail("Falha ao iniciar o banco");
    process.exit(1);
  }

  // Aguardar postgres ficar pronto
  s = spinner("Aguardando PostgreSQL ficar pronto...");
  let ready = false;
  for (let i = 0; i < 20; i++) {
    try {
      run("docker compose exec -T db pg_isready -U openmonetis", { cwd: targetDir });
      ready = true;
      break;
    } catch {
      await new Promise((r) => setTimeout(r, 1500));
    }
  }
  if (!ready) {
    s.fail("PostgreSQL não respondeu a tempo");
    process.exit(1);
  }
  s.stop("PostgreSQL pronto");

  // Extensões
  s = spinner("Habilitando extensões do banco...");
  try {
    run("pnpm db:extensions", { cwd: targetDir });
    s.stop("Extensões habilitadas");
  } catch {
    s.fail("Falha ao habilitar extensões");
    process.exit(1);
  }
}

// db:push
s = spinner("Aplicando schema no banco...");
try {
  run("pnpm db:push", { cwd: targetDir });
  s.stop("Schema aplicado");
} catch {
  s.fail("Falha ao aplicar schema");
  process.exit(1);
}

// ─── Finalização ──────────────────────────────────────────────────────────────

console.log(`
${c.green}${c.bold}  ✔ OpenMonetis instalado com sucesso!${c.reset}

  ${c.bold}Para iniciar:${c.reset}
    cd openmonetis
    pnpm dev${
      useLocalDocker
        ? `          ${c.dim}→ desenvolvimento${c.reset}\n    pnpm docker:up    ${c.dim}→ produção local (app + banco)${c.reset}`
        : `          ${c.dim}→ desenvolvimento${c.reset}`
    }

  ${c.bold}Acesse:${c.reset}  ${betterAuthUrl}
  ${c.bold}Docs:${c.reset}    https://github.com/felipegcoutinho/openmonetis
`);
