import { readdir, readFile } from 'fs/promises'
import { existsSync } from 'fs'
import { join } from 'path'
import { isValidSchedule, toCronExpr, type CronJob, type InvalidJob, type Schedule } from './schedules'

const APPS_DIR = process.env.APPS_DIR!

const SCHEDULE_RE = /export\s+const\s+schedule\s*=\s*['"]([^'"]+)['"]/

export async function getApps(): Promise<string[]> {
  const entries = await readdir(APPS_DIR, { withFileTypes: true })
  const apps: string[] = []

  for (const entry of entries) {
    if (!entry.isDirectory()) continue
    if (existsSync(join(APPS_DIR, entry.name, 'package.json'))) {
      apps.push(entry.name)
    }
  }

  return apps.sort()
}

export type DiscoveryResult = {
  jobs: CronJob[]
  invalid: InvalidJob[]
}

export async function discoverCronJobs(): Promise<DiscoveryResult> {
  const jobs: CronJob[] = []
  const invalid: InvalidJob[] = []
  const apps = await readdir(APPS_DIR, { withFileTypes: true })

  for (const app of apps) {
    if (!app.isDirectory()) continue

    const cronDir = join(APPS_DIR, app.name, 'cron')
    if (!existsSync(cronDir)) continue

    const files = await readdir(cronDir)
    for (const file of files) {
      if (!file.endsWith('.ts')) continue

      const filePath = join(cronDir, file)
      const name = file.replace(/\.ts$/, '')
      const id = `${app.name}:${name}`

      try {
        const source = await readFile(filePath, 'utf-8')
        const match = source.match(SCHEDULE_RE)

        if (!match) {
          invalid.push({ id, app: app.name, name, file: filePath, error: 'Missing schedule export' })
          continue
        }

        const schedule = match[1] as Schedule

        if (!isValidSchedule(schedule)) {
          invalid.push({ id, app: app.name, name, file: filePath, error: `Invalid schedule: "${match[1]}"` })
          continue
        }

        jobs.push({
          id,
          app: app.name,
          name,
          file: filePath,
          schedule,
          cronExpr: toCronExpr(schedule),
          state: 'idle',
        })
      } catch (e) {
        const msg = e instanceof Error ? e.message : String(e)
        invalid.push({ id, app: app.name, name, file: filePath, error: msg })
      }
    }
  }

  return { jobs, invalid }
}
