import { config as loadDotEnv } from 'dotenv'
import { execFileSync } from 'node:child_process'
import { createRequire } from 'node:module'
import { createConnection } from 'mysql2/promise'

loadDotEnv()

const require = createRequire(import.meta.url)
const prismaCliPath = require.resolve('prisma/build/index.js')

function readDatabaseUrl(): URL {
  const raw = process.env.DATABASE_URL?.trim()

  if (!raw) {
    throw new Error('DATABASE_URL is required.')
  }

  return new URL(raw)
}

function getDatabaseName(databaseUrl: URL): string {
  const name = databaseUrl.pathname.replace(/^\//, '').trim()

  if (!name) {
    throw new Error(
      'DATABASE_URL must include an application database name, for example mysql://user:pass@host:3306/spottyfy.',
    )
  }

  return name
}

function getServerUrl(databaseUrl: URL): URL {
  const serverUrl = new URL(databaseUrl.toString())
  serverUrl.pathname = '/'
  serverUrl.search = ''
  serverUrl.hash = ''
  return serverUrl
}

function getConnectionUser(databaseUrl: URL): string {
  const user = decodeURIComponent(databaseUrl.username)

  if (!user) {
    throw new Error('DATABASE_URL must include a database username.')
  }

  return user
}

function getConnectionHost(databaseUrl: URL): string {
  return databaseUrl.hostname || 'localhost'
}

function getConnectionPort(databaseUrl: URL): number {
  return Number(databaseUrl.port || '3306')
}

async function createDatabase(connectionUrl: URL, databaseName: string): Promise<void> {
  const connection = await createConnection({
    host: getConnectionHost(connectionUrl),
    port: getConnectionPort(connectionUrl),
    user: decodeURIComponent(connectionUrl.username),
    password: decodeURIComponent(connectionUrl.password),
    multipleStatements: false,
  })

  try {
    await connection.query(
      `CREATE DATABASE IF NOT EXISTS \`${databaseName.replace(/`/g, '``')}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`,
    )
  } finally {
    await connection.end()
  }
}

async function ensureDatabaseExists(): Promise<void> {
  const databaseUrl = readDatabaseUrl()
  const databaseName = getDatabaseName(databaseUrl)
  const bootstrapConnectionUrl = process.env.DATABASE_BOOTSTRAP_URL?.trim()

  try {
    await createDatabase(databaseUrl, databaseName)
    return
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error)

    if (message.toLowerCase().includes('access denied') && bootstrapConnectionUrl) {
      await createDatabase(new URL(bootstrapConnectionUrl), databaseName)
      return
    }

    if (message.toLowerCase().includes('access denied')) {
      throw new Error(
        'Could not create the application database with DATABASE_URL. Provide DATABASE_BOOTSTRAP_URL for a MariaDB account with permission to create the app database, or create it manually.',
      )
    }

    throw error
  }
}

function runPrismaMigrations(): void {
  const primaryDatabaseUrl = process.env.DATABASE_URL?.trim()
  const bootstrapConnectionUrl = process.env.DATABASE_BOOTSTRAP_URL?.trim()

  if (!primaryDatabaseUrl) {
    throw new Error('DATABASE_URL or DATABASE_BOOTSTRAP_URL is required.')
  }

  const effectiveDatabaseUrl = bootstrapConnectionUrl
    ? (() => {
        const primary = new URL(primaryDatabaseUrl)
        const bootstrap = new URL(bootstrapConnectionUrl)
        bootstrap.pathname = primary.pathname
        bootstrap.search = primary.search
        bootstrap.hash = primary.hash
        return bootstrap.toString()
      })()
    : primaryDatabaseUrl

  const migrationEnv = {
    ...process.env,
    DATABASE_URL: effectiveDatabaseUrl,
  }

  execFileSync(
    process.execPath,
    [prismaCliPath, 'generate', '--schema', 'server/prisma/schema.prisma'],
    {
      stdio: 'inherit',
      env: migrationEnv,
    },
  )

  execFileSync(
    process.execPath,
    [prismaCliPath, 'migrate', 'deploy', '--schema', 'server/prisma/schema.prisma'],
    {
      stdio: 'inherit',
      env: migrationEnv,
    },
  )
}

async function main(): Promise<void> {
  await ensureDatabaseExists()
  runPrismaMigrations()
}

main().catch((error) => {
  // eslint-disable-next-line no-console
  console.error(error instanceof Error ? error.message : error)
  process.exit(1)
})