#!/usr/bin/env node

const { spawnSync } = require('node:child_process')
const { readdirSync } = require('node:fs')
const path = require('node:path')
const {
  ensureDatabaseUrl,
  loadDefaultEnvFiles,
  normalizeDatabaseUrlForExecution,
} = require('../src/runtime-config')
const { PrismaClient } = require('../generated')

const repoRoot = path.resolve(__dirname, '../../..')
const prismaBin = path.join(repoRoot, 'node_modules', '.bin', 'prisma')
const schemaPath = path.join(repoRoot, 'packages/database/prisma/schema.prisma')
const migrationsDir = path.join(repoRoot, 'packages/database/prisma/migrations')

function getDatabaseName(databaseUrl) {
  try {
    const parsed = new URL(databaseUrl)
    const databaseName = parsed.pathname.replace(/^\/+/, '')
    return databaseName ? decodeURIComponent(databaseName) : null
  } catch {
    return null
  }
}

function getMaintenanceDatabaseUrl(databaseUrl) {
  try {
    const parsed = new URL(databaseUrl)
    parsed.pathname = '/postgres'
    return parsed.toString()
  } catch {
    return databaseUrl
  }
}

function quoteIdentifier(value) {
  return `"${String(value).replace(/"/g, '""')}"`
}

function run(command, args) {
  const result = spawnSync(command, args, {
    stdio: 'inherit',
    cwd: repoRoot,
    env: process.env,
  })

  if (result.status !== 0) {
    process.exit(result.status ?? 1)
  }
}

async function main() {
  loadDefaultEnvFiles(repoRoot)
  ensureDatabaseUrl()
  normalizeDatabaseUrlForExecution()
  const targetDatabaseUrl = process.env.DATABASE_URL
  const targetDatabaseName = getDatabaseName(targetDatabaseUrl)

  if (targetDatabaseUrl && targetDatabaseName && targetDatabaseName !== 'postgres') {
    const adminPrisma = new PrismaClient({
      datasources: {
        db: {
          url: getMaintenanceDatabaseUrl(targetDatabaseUrl),
        },
      },
    })

    try {
      const existingDatabase = await adminPrisma.$queryRaw`
        SELECT datname
        FROM pg_database
        WHERE datname = ${targetDatabaseName}
        LIMIT 1
      `

      if (existingDatabase.length === 0) {
        console.log(`[db:deploy] Creating missing database ${targetDatabaseName}`)
        await adminPrisma.$executeRawUnsafe(
          `CREATE DATABASE ${quoteIdentifier(targetDatabaseName)}`,
        )
      }
    } finally {
      await adminPrisma.$disconnect()
    }
  }

  const prisma = new PrismaClient()

  try {
    const [state] = await prisma.$queryRawUnsafe(`
      SELECT
        to_regclass('public.companies') IS NOT NULL AS "hasCompanies",
        to_regclass('public.employees') IS NOT NULL AS "hasEmployees",
        to_regclass('public._prisma_migrations') IS NOT NULL AS "hasMigrationsTable"
    `)

    const failedMigrations = state.hasMigrationsTable
      ? await prisma.$queryRawUnsafe(`
          SELECT migration_name
          FROM "_prisma_migrations"
          WHERE finished_at IS NULL
            AND rolled_back_at IS NULL
          ORDER BY started_at
        `)
      : []

    const hasBaseSchema = state.hasCompanies || state.hasEmployees

    if (!hasBaseSchema) {
      if (failedMigrations.length > 0) {
        console.log('[db:deploy] Fresh database with failed migration records detected; marking them rolled back before bootstrap')
        for (const migration of failedMigrations) {
          run(prismaBin, [
            'migrate',
            'resolve',
            '--schema',
            schemaPath,
            '--rolled-back',
            migration.migration_name,
          ])
        }
      }

      console.log('[db:deploy] Fresh database detected; bootstrapping schema with prisma db push')
      run(prismaBin, ['db', 'push', '--schema', schemaPath])

      const migrationNames = readdirSync(migrationsDir, { withFileTypes: true })
        .filter((entry) => entry.isDirectory())
        .map((entry) => entry.name)
        .sort()

      if (migrationNames.length > 0) {
        console.log('[db:deploy] Marking existing migrations as applied for the bootstrapped schema')
        for (const migrationName of migrationNames) {
          run(prismaBin, [
            'migrate',
            'resolve',
            '--schema',
            schemaPath,
            '--applied',
            migrationName,
          ])
        }
      }

      return
    }

    if (failedMigrations.length > 0) {
      console.error('[db:deploy] Found failed migrations in a non-empty database. Resolve them manually before deploying again.')
      for (const migration of failedMigrations) {
        console.error(`- ${migration.migration_name}`)
      }
      process.exit(1)
    }

    console.log('[db:deploy] Existing database detected; applying Prisma migrations')
    run(prismaBin, ['migrate', 'deploy', '--schema', schemaPath])
  } finally {
    await prisma.$disconnect()
  }
}

main().catch((error) => {
  if (error?.message?.includes("Can't reach database server at `localhost:5432`")) {
    console.error('[db:deploy] Hint: start the local Postgres service first. In this repo that is usually `docker compose -f docker-compose.dev.yml up -d postgres`.')
  }
  console.error('[db:deploy] Failed:', error)
  process.exit(1)
})
