import { exec } from 'node:child_process'
import { promises as fs } from 'node:fs'
import { promisify } from 'node:util'
import readline from 'node:readline'
import crypto from 'node:crypto'
import path from 'node:path'
import os from 'node:os'

const execAsync = promisify(exec)

function question(query: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  })

  return new Promise(resolve =>
    rl.question(query, (ans) => {
      rl.close()
      resolve(ans)
    })
  )
}

async function checkStripeCLI() {
  console.log(
    'Step 1: Checking if Stripe CLI is installed and authenticated...'
  )
  try {
    await execAsync('stripe --version')
    console.log('Stripe CLI is installed.')

    // Check if Stripe CLI is authenticated
    try {
      await execAsync('stripe config --list')
      console.log('Stripe CLI is authenticated.')
    } catch {
      console.log(
        'Stripe CLI is not authenticated or the authentication has expired.'
      )
      console.log('Please run: stripe login')
      const answer = await question(
        'Have you completed the authentication? (y/n): '
      )
      if (answer.toLowerCase() !== 'y') {
        console.log(
          'Please authenticate with Stripe CLI and run this script again.'
        )
        process.exit(1)
      }

      // Verify authentication after user confirms login
      try {
        await execAsync('stripe config --list')
        console.log('Stripe CLI authentication confirmed.')
      } catch {
        console.error(
          'Failed to verify Stripe CLI authentication. Please try again.'
        )
        process.exit(1)
      }
    }
  } catch {
    console.error(
      'Stripe CLI is not installed. Please install it and try again.'
    )
    console.log('To install Stripe CLI, follow these steps:')
    console.log('1. Visit: https://docs.stripe.com/stripe-cli')
    console.log(
      '2. Download and install the Stripe CLI for your operating system'
    )
    console.log('3. After installation, run: stripe login')
    console.log(
      'After installation and authentication, please run this setup script again.'
    )
    process.exit(1)
  }
}

async function getPostgresURL(): Promise<string> {
  console.log('Step 2: Setting up Postgres')
  const dbChoice = await question(
    'Do you want to use a local Postgres instance with Docker (L) or a remote Postgres instance (R)? (L/R): '
  )

  if (dbChoice.toLowerCase() === 'l') {
    console.log('Setting up local Postgres instance with Docker...')
    await setupLocalPostgres()
    return 'postgres://postgres:postgres@localhost:54322/postgres'
  } else {
    console.log(
      'You can find Postgres databases at: https://vercel.com/marketplace?category=databases'
    )
    return await question('Enter your NUXT_DATABASE_URL: ')
  }
}

async function setupLocalPostgres() {
  console.log('Checking if Docker is installed...')
  try {
    await execAsync('docker --version')
    console.log('Docker is installed.')
  } catch {
    console.error(
      'Docker is not installed. Please install Docker and try again.'
    )
    console.log(
      'To install Docker, visit: https://docs.docker.com/get-docker/'
    )
    process.exit(1)
  }

  console.log('Creating docker-compose.yml file...')
  const dockerComposeContent = `
services:
  postgres:
    image: postgres:16.4-alpine
    container_name: next_saas_starter_postgres
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "54322:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
`

  await fs.writeFile(
    path.join(process.cwd(), 'docker-compose.yml'),
    dockerComposeContent
  )
  console.log('docker-compose.yml file created.')

  console.log('Starting Docker container with `docker compose up -d`...')
  try {
    await execAsync('docker compose up -d')
    console.log('Docker container started successfully.')
  } catch {
    console.error(
      'Failed to start Docker container. Please check your Docker installation and try again.'
    )
    process.exit(1)
  }
}

async function getStripeSecretKey(): Promise<string> {
  console.log('Step 3: Getting Stripe Secret Key')
  console.log(
    'You can find your Stripe Secret Key at: https://dashboard.stripe.com/test/apikeys'
  )
  return await question('Enter your Stripe Secret Key: ')
}

async function createStripeWebhook(): Promise<string> {
  console.log('Step 4: Creating Stripe webhook...')
  try {
    const { stdout } = await execAsync('stripe listen --print-secret')
    const match = stdout.match(/whsec_[a-zA-Z0-9]+/)
    if (!match) {
      throw new Error('Failed to extract Stripe webhook secret')
    }
    console.log('Stripe webhook created.')
    return match[0]
  } catch (error) {
    console.error(
      'Failed to create Stripe webhook. Check your Stripe CLI installation and permissions.'
    )
    if (os.platform() === 'win32') {
      console.log(
        'Note: On Windows, you may need to run this script as an administrator.'
      )
    }
    throw error
  }
}

function generateBetterAuthSecret(): string {
  console.log('Step 5: Generating BETTER_AUTH_SECRET...')
  return crypto.randomBytes(32).toString('hex')
}

async function writeEnvFile(envVars: Record<string, string>) {
  console.log('Step 6: Writing environment variables to .env')
  const envContent = Object.entries(envVars)
    .map(([key, value]) => `${key}=${value}`)
    .join('\n')

  await fs.writeFile(path.join(process.cwd(), '.env'), envContent)
  console.log('.env file created with the necessary variables.')
}

async function readExistingEnv(): Promise<Record<string, string>> {
  try {
    const envPath = path.join(process.cwd(), '.env')
    const content = await fs.readFile(envPath, 'utf8')
    const map: Record<string, string> = {}
    for (const line of content.split('\n')) {
      const trimmed = line.trim()
      if (!trimmed || trimmed.startsWith('#')) continue
      const idx = trimmed.indexOf('=')
      if (idx === -1) continue
      const key = trimmed.slice(0, idx)
      const value = trimmed.slice(idx + 1)
      map[key] = value
    }
    return map
  } catch {
    return {}
  }
}

async function resolveEnvVar(
  key: string,
  existingValue: string | undefined,
  producer: () => Promise<string>
): Promise<string> {
  if (existingValue && existingValue.length > 0) {
    const ans = await question(
      `${key} is already set in .env. Keep existing value? (y/n): `
    )
    if (ans.toLowerCase().startsWith('y')) {
      console.log(`Keeping existing ${key}.`)
      return existingValue
    }
    console.log(`Overriding ${key}...`)
  }
  return await producer()
}

async function main() {
  await checkStripeCLI()

  // Read existing .env to preserve and optionally keep variables
  const existingEnv = await readExistingEnv()

  const NUXT_DATABASE_URL = await resolveEnvVar(
    'NUXT_DATABASE_URL',
    existingEnv.NUXT_DATABASE_URL,
    getPostgresURL
  )

  const NUXT_STRIPE_SECRET_KEY = await resolveEnvVar(
    'NUXT_STRIPE_SECRET_KEY',
    existingEnv.NUXT_STRIPE_SECRET_KEY,
    getStripeSecretKey
  )

  const NUXT_STRIPE_WEBHOOK_SECRET = await resolveEnvVar(
    'NUXT_STRIPE_WEBHOOK_SECRET',
    existingEnv.NUXT_STRIPE_WEBHOOK_SECRET,
    createStripeWebhook
  )

  const NUXT_BETTER_AUTH_URL = await resolveEnvVar(
    'NUXT_BETTER_AUTH_URL',
    existingEnv.NUXT_BETTER_AUTH_URL,
    async () => {
      const val = await question(
        'Enter your Better Auth URL (default http://localhost:3000): '
      )
      return val && val.trim().length > 0 ? val.trim() : 'http://localhost:3000'
    }
  )

  const NUXT_BETTER_AUTH_SECRET = await resolveEnvVar(
    'NUXT_BETTER_AUTH_SECRET',
    existingEnv.NUXT_BETTER_AUTH_SECRET,
    async () => generateBetterAuthSecret()
  )

  const mergedEnv = {
    ...existingEnv,
    NUXT_DATABASE_URL,
    NUXT_STRIPE_SECRET_KEY,
    NUXT_STRIPE_WEBHOOK_SECRET,
    NUXT_BETTER_AUTH_URL,
    NUXT_BETTER_AUTH_SECRET
  }

  await writeEnvFile(mergedEnv)

  console.log('🎉 Setup completed successfully!')
}

main().catch(console.error)
