import net from 'node:net'
import path from 'node:path'

export const REQUIRED_NODE_MAJOR = 22
export const DEFAULT_LOCAL_PORT = 4000
export const DEFAULT_VITE_PORT = 5180
export const CLOUDFLARE_DATABASE_NAME = 'aipro-skill-db'
export const CLOUDFLARE_R2_BUCKET = 'aipro-skill-pkg'
export const CLOUDFLARE_PAGES_PROJECT = 'aipros-lite-student'

export function parseNodeMajor(version = process.version) {
  const match = String(version || '').match(/^v?(\d+)/)
  return match ? Number(match[1]) : 0
}

export function formatReleaseStamp(date = new Date(), timeZone = 'Asia/Shanghai') {
  const parts = new Intl.DateTimeFormat('en-CA', {
    timeZone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hourCycle: 'h23',
  }).formatToParts(date)
  const values = Object.fromEntries(parts.map(part => [part.type, part.value]))
  return `${values.year}${values.month}${values.day}-${values.hour}${values.minute}${values.second}`
}

export function normalizeReleasePath(relPath) {
  return String(relPath || '')
    .replaceAll('\\', '/')
    .replace(/^\.\//, '')
    .replace(/\/+/g, '/')
}

export function isReleaseExcluded(relPath) {
  const p = normalizeReleasePath(relPath)
  if (!p || p === '.') return false

  if (p === '.git' || p.startsWith('.git/')) return true
  if (p === '.worktrees' || p.startsWith('.worktrees/')) return true
  if (p === '.DS_Store' || p.endsWith('/.DS_Store')) return true
  if (p === 'node_modules' || p.startsWith('node_modules/')) return true
  if (p === 'cf-worker/node_modules' || p.startsWith('cf-worker/node_modules/')) return true
  if (p === 'dist' || p.startsWith('dist/')) return true
  if (p === 'release' || p.startsWith('release/')) return true
  if (p === '.wrangler' || p.startsWith('.wrangler/')) return true
  if (p === 'cf-worker/.wrangler' || p.startsWith('cf-worker/.wrangler/')) return true

  if (p === '.env' || /^\.env\./.test(p)) return true
  if (p === 'cf-worker/.env' || p.startsWith('cf-worker/.env.')) return true

  if (p === 'cf-worker/data' || p.startsWith('cf-worker/data/')) return true
  if (p === 'cf-worker/uploads' || p.startsWith('cf-worker/uploads/')) return true
  if (p === 'uploads' || p.startsWith('uploads/')) return true
  if (p === 'outputs' || p.startsWith('outputs/')) return true

  if (p === 'deploy/cert.zip') return true
  if (p === 'deploy/ssl' || p.startsWith('deploy/ssl/')) return true
  if (/\.(key|pem|p12|pfx|crt)$/i.test(p)) return true
  if (/\.(log|sqlite|sqlite-shm|sqlite-wal|db|db-shm|db-wal)$/i.test(p)) return true

  return false
}

export function renderRootEnv({ port = DEFAULT_LOCAL_PORT } = {}) {
  return [
    '# Generated by npm run local. Safe to delete and regenerate.',
    'VITE_API_BASE=/api',
    `VITE_PUBLIC_BASE_URL=http://127.0.0.1:${port}`,
    '',
  ].join('\n')
}

export function renderWorkerEnv({ port = DEFAULT_LOCAL_PORT, rootDir }) {
  const root = path.resolve(rootDir || process.cwd())
  const workerDir = path.join(root, 'cf-worker')
  return [
    '# Generated by npm run local. Safe to delete and regenerate.',
    `PORT=${port}`,
    `DB_PATH=${path.join(workerDir, 'data', 'aipro.db')}`,
    `UPLOADS_DIR=${path.join(workerDir, 'uploads')}`,
    `DIST_DIR=${path.join(root, 'dist')}`,
    `ALLOW_ORIGINS=http://localhost:${port},http://127.0.0.1:${port}`,
    'R2_PUBLIC_BASE=',
    '',
  ].join('\n')
}

export function parseWranglerD1DatabaseId(output) {
  const text = String(output || '')
  const tomlMatch = text.match(/database_id\s*=\s*["']([0-9a-fA-F-]{32,36})["']/)
  if (tomlMatch) return tomlMatch[1]
  const labelMatch = text.match(/database_id[:\s]+([0-9a-fA-F-]{32,36})/)
  if (labelMatch) return labelMatch[1]
  const uuidMatch = text.match(/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/)
  return uuidMatch ? uuidMatch[0] : ''
}

export function parseWranglerWorkerUrl(output) {
  const match = String(output || '').match(/https:\/\/[a-zA-Z0-9.-]+\.workers\.dev\b/)
  return match ? match[0].replace(/\/$/, '') : ''
}

export function parseWranglerPagesUrl(output) {
  const match = String(output || '').match(/https:\/\/[a-zA-Z0-9.-]+\.pages\.dev\b/)
  return match ? match[0].replace(/\/$/, '') : ''
}

export function patchWranglerToml(toml, { databaseId, allowOrigins } = {}) {
  let next = String(toml || '')
  if (databaseId) {
    if (/database_id\s*=\s*["'][^"']*["']/.test(next)) {
      next = next.replace(/database_id\s*=\s*["'][^"']*["']/, `database_id = "${databaseId}"`)
    } else {
      next += `\ndatabase_id = "${databaseId}"\n`
    }
  }
  if (allowOrigins) {
    if (/ALLOW_ORIGINS\s*=\s*["'][^"']*["']/.test(next)) {
      next = next.replace(/ALLOW_ORIGINS\s*=\s*["'][^"']*["']/, `ALLOW_ORIGINS = "${allowOrigins}"`)
    } else {
      next += `\nALLOW_ORIGINS = "${allowOrigins}"\n`
    }
  }
  return next
}

export function sanitizeD1SeedSql(sql) {
  return String(sql || '')
    .split(/\r?\n/)
    .filter((line) => !/^\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE)\s*;?\s*$/i.test(line))
    .filter((line) => !/^\s*PRAGMA\b/i.test(line))
    .join('\n')
    .trimStart()
    .concat('\n')
}

export function cloudflareSeedCommandArgs(
  databaseName = CLOUDFLARE_DATABASE_NAME,
  filePath = './.tmp/categories.cloudflare.sql',
) {
  return [
    'wrangler',
    'd1',
    'execute',
    databaseName,
    '--remote',
    `--file=${filePath}`,
  ]
}

export async function findAvailablePort(start = DEFAULT_LOCAL_PORT, limit = 30) {
  for (let port = start; port < start + limit; port += 1) {
    if (await canListen(port)) return port
  }
  throw new Error(`没有找到可用端口：${start}-${start + limit - 1}`)
}

function canListen(port) {
  return new Promise((resolve) => {
    const server = net.createServer()
    server.once('error', () => resolve(false))
    server.once('listening', () => {
      server.close(() => resolve(true))
    })
    server.listen(port, '127.0.0.1')
  })
}
