import Fastify from 'fastify'
import { drizzle } from 'drizzle-orm/postgres-js'
import { migrate } from 'drizzle-orm/postgres-js/migrator'
import postgres from 'postgres'
import rateLimit from '@fastify/rate-limit'
import { databasePlugin } from './plugins/database.js'
import { redisPlugin } from './plugins/redis.js'
import { corsPlugin } from './plugins/cors.js'
import { errorHandlerPlugin } from './plugins/error-handler.js'
import { authPlugin } from './plugins/auth.js'
import { devAuthPlugin } from './plugins/dev-auth.js'
import { storagePlugin } from './plugins/storage.js'
import { healthRoutes } from './routes/v1/health.js'
import { versionRoutes } from './routes/v1/version.js'
import { authRoutes } from './routes/v1/auth.js'
import { accountRoutes } from './routes/v1/accounts.js'
import { inventoryRoutes } from './routes/v1/inventory.js'
import { productRoutes } from './routes/v1/products.js'
import { lookupRoutes } from './routes/v1/lookups.js'
import { fileRoutes } from './routes/v1/files.js'
import { rbacRoutes } from './routes/v1/rbac.js'
import { repairRoutes } from './routes/v1/repairs.js'
import { lessonRoutes } from './routes/v1/lessons.js'
import { transactionRoutes } from './routes/v1/transactions.js'
import { drawerRoutes } from './routes/v1/drawer.js'
import { registerRoutes } from './routes/v1/register.js'
import { reportRoutes } from './routes/v1/reports.js'
import { discountRoutes } from './routes/v1/discounts.js'
import { taxRoutes } from './routes/v1/tax.js'
import { storageRoutes } from './routes/v1/storage.js'
import { storeRoutes } from './routes/v1/store.js'
import { vaultRoutes } from './routes/v1/vault.js'
import { webdavRoutes } from './routes/webdav/index.js'
import { moduleRoutes } from './routes/v1/modules.js'
import { configRoutes } from './routes/v1/config.js'
import { invoiceRoutes } from './routes/v1/invoices.js'
import { RbacService } from './services/rbac.service.js'
import { ModuleService } from './services/module.service.js'
import { AppConfigService } from './services/config.service.js'
import { SettingsService } from './services/settings.service.js'
import { eq } from 'drizzle-orm'
import { users } from './db/schema/users.js'
import { companies } from './db/schema/stores.js'
import { roles, userRoles } from './db/schema/rbac.js'
import { EmailService } from './services/email.service.js'
import bcrypt from 'bcryptjs'

async function seedInitialUser(app: Awaited<ReturnType<typeof buildApp>>) {
  const email = process.env.INITIAL_USER_EMAIL
  const firstName = process.env.INITIAL_USER_FIRST_NAME
  const lastName = process.env.INITIAL_USER_LAST_NAME
  if (!email || !firstName || !lastName) return

  const existing = await app.db.select({ id: users.id }).from(users).limit(1)
  if (existing.length > 0) return

  // Create user with a random password — they'll set their real one via the welcome email
  const tempPassword = crypto.randomUUID()
  const passwordHash = await bcrypt.hash(tempPassword, 10)
  const [user] = await app.db.insert(users).values({ email, passwordHash, firstName, lastName, role: 'admin' }).returning({ id: users.id })

  // Assign the Admin RBAC role
  const [adminRole] = await app.db.select({ id: roles.id }).from(roles).where(eq(roles.name, 'Admin')).limit(1)
  if (adminRole) {
    await app.db.insert(userRoles).values({ userId: user.id, roleId: adminRole.id })
  }

  app.log.info({ email }, 'Initial admin user created')

  // Send welcome email with password setup link
  try {
    const resetToken = app.jwt.sign({ userId: user.id, purpose: 'password-reset' } as any, { expiresIn: '4h' })
    const appUrl = process.env.APP_URL ?? `https://${process.env.HOSTNAME ?? 'localhost'}`
    const resetLink = `${appUrl}/reset-password?token=${resetToken}`

    const [store] = await app.db.select({ name: companies.name }).from(companies).limit(1)
    const storeName = store?.name ?? process.env.BUSINESS_NAME ?? 'LunarFront'

    await EmailService.send(app.db, {
      to: email,
      subject: `Welcome to ${storeName} — Set your password`,
      html: `
        <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 480px; margin: 0 auto; padding: 40px 20px;">
          <h2 style="color: #1a1a2e; margin-bottom: 8px;">${storeName}</h2>
          <p style="color: #555; margin-bottom: 24px;">Hi ${firstName},</p>
          <p style="color: #555;">Your account has been created. Click the button below to set your password and get started:</p>
          <div style="text-align: center; margin: 32px 0;">
            <a href="${resetLink}" style="background-color: #1a1a2e; color: #fff; padding: 12px 32px; border-radius: 6px; text-decoration: none; font-weight: 500;">Set Your Password</a>
          </div>
          <p style="color: #888; font-size: 13px;">This link expires in 4 hours. If it expires, you can request a new one from the login page.</p>
          <hr style="border: none; border-top: 1px solid #eee; margin: 32px 0;" />
          <p style="color: #aaa; font-size: 11px;">Powered by LunarFront</p>
        </div>
      `,
      text: `Hi ${firstName}, welcome to ${storeName}! Set your password here: ${resetLink} — This link expires in 4 hours.`,
    })
    app.log.info({ email }, 'Welcome email sent to initial user')
  } catch (err) {
    app.log.error({ email, error: (err as Error).message }, 'Failed to send welcome email — user can use forgot password')
  }
}

async function seedEmailSettings(app: Awaited<ReturnType<typeof buildApp>>) {
  const apiKey = process.env.RESEND_API_KEY
  if (!apiKey) return

  const existing = await SettingsService.get(app.db, 'email.provider')
  if (existing) return

  await SettingsService.set(app.db, 'email.provider', 'resend')
  await SettingsService.set(app.db, 'email.resend_api_key', apiKey, true)
  if (process.env.MAIL_FROM) {
    await SettingsService.set(app.db, 'email.from_address', process.env.MAIL_FROM)
  }
  if (process.env.BUSINESS_NAME) {
    await SettingsService.set(app.db, 'email.business_name', process.env.BUSINESS_NAME)
  }
  app.log.info('Email settings seeded from environment')
}

async function seedCompany(app: Awaited<ReturnType<typeof buildApp>>) {
  const name = process.env.BUSINESS_NAME
  if (!name) return

  const existing = await app.db.select({ id: companies.id }).from(companies).limit(1)
  if (existing.length > 0) return

  await app.db.insert(companies).values({ name })
  app.log.info({ name }, 'Company seeded from environment')
}

export async function buildApp() {
  const app = Fastify({
    logger: {
      level: process.env.LOG_LEVEL ?? 'info',
      ...(process.env.NODE_ENV === 'development'
        ? { transport: { target: 'pino-pretty' } }
        : process.env.LOG_FILE
          ? {
              transport: {
                targets: [
                  { target: 'pino/file', options: { destination: 1 } }, // stdout
                  { target: 'pino/file', options: { destination: process.env.LOG_FILE, mkdir: true } },
                ],
              },
            }
          : {}),
    },
    genReqId: () => crypto.randomUUID(),
    requestTimeout: 30000, // 30 seconds
  })

  // Plugins
  await app.register(corsPlugin)
  await app.register(errorHandlerPlugin)
  await app.register(databasePlugin)
  await app.register(redisPlugin)
  await app.register(rateLimit, { global: false })
  await app.register(storagePlugin)

  // Auth — JWT in production/test, dev bypass only in development without JWT_SECRET
  if (process.env.JWT_SECRET) {
    await app.register(authPlugin)
  } else if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
    app.log.warn('JWT_SECRET not set — using dev auth bypass. DO NOT USE IN PRODUCTION.')
    await app.register(devAuthPlugin)
  } else {
    throw new Error('JWT_SECRET is required in production')
  }

  // Module gate middleware — returns 403 if module is disabled
  function requireModule(slug: string) {
    return async (_request: any, reply: any) => {
      const enabled = await ModuleService.isEnabled(app.db, slug)
      if (!enabled) {
        reply.status(403).send({ error: { message: `Module '${slug}' is not enabled`, statusCode: 403, module: slug } })
      }
    }
  }

  // Helper: wrap a route plugin so every route in it gets a module check.
  // Uses fastify-plugin (fp) pattern to inherit parent decorators while adding the hook.
  function withModule(slug: string, plugin: any) {
    const wrapped = async (instance: any, opts: any) => {
      instance.addHook('onRequest', requireModule(slug))
      await plugin(instance, opts)
    }
    // Copy the plugin's symbol to maintain encapsulation behavior
    if (plugin[Symbol.for('skip-override')]) {
      wrapped[Symbol.for('skip-override')] = true
    }
    // Copy plugin name for debugging
    Object.defineProperty(wrapped, 'name', { value: `${plugin.name || 'anonymous'}[${slug}]` })
    return wrapped
  }

  // Core routes — always available
  await app.register(healthRoutes, { prefix: '/v1' })
  await app.register(versionRoutes, { prefix: '/v1' })
  await app.register(authRoutes, { prefix: '/v1' })
  await app.register(accountRoutes, { prefix: '/v1' })
  await app.register(rbacRoutes, { prefix: '/v1' })
  await app.register(storeRoutes, { prefix: '/v1' })
  await app.register(moduleRoutes, { prefix: '/v1' })
  await app.register(configRoutes, { prefix: '/v1' })
  await app.register(invoiceRoutes, { prefix: '/v1' })
  await app.register(lookupRoutes, { prefix: '/v1' })

  // Module-gated routes
  await app.register(withModule('inventory', inventoryRoutes), { prefix: '/v1' })
  await app.register(withModule('inventory', productRoutes), { prefix: '/v1' })
  await app.register(withModule('files', fileRoutes), { prefix: '/v1' })
  await app.register(withModule('files', storageRoutes), { prefix: '/v1' })
  await app.register(withModule('repairs', repairRoutes), { prefix: '/v1' })
  await app.register(withModule('lessons', lessonRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', transactionRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', drawerRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', registerRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', reportRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', discountRoutes), { prefix: '/v1' })
  await app.register(withModule('pos', taxRoutes), { prefix: '/v1' })
  await app.register(withModule('vault', vaultRoutes), { prefix: '/v1' })
  // Register WebDAV custom HTTP methods before routes
  app.addHttpMethod('PROPFIND', { hasBody: true })
  app.addHttpMethod('PROPPATCH', { hasBody: true })
  app.addHttpMethod('MKCOL', { hasBody: true })
  app.addHttpMethod('COPY')
  app.addHttpMethod('MOVE')
  app.addHttpMethod('LOCK', { hasBody: true })
  app.addHttpMethod('UNLOCK')
  await app.register(webdavRoutes, { prefix: '/webdav' })

  // Auto-seed system permissions and warm module cache on startup
  app.addHook('onReady', async () => {
    try {
      await RbacService.seedPermissions(app.db)
      await RbacService.seedDefaultRoles(app.db)
      app.log.info('System permissions and roles seeded')
    } catch (err) {
      app.log.error({ err }, 'Failed to seed permissions/roles')
    }
    try {
      await ModuleService.refreshCache(app.db)
      const enabled = await ModuleService.getEnabledSlugs(app.db)
      app.log.info({ modules: [...enabled] }, 'Module cache loaded')
    } catch (err) {
      app.log.error({ err }, 'Failed to load module cache')
    }
    try {
      await AppConfigService.refreshCache(app.db)
      const dbLogLevel = await AppConfigService.get(app.db, 'log_level')
      if (dbLogLevel) {
        app.log.level = dbLogLevel
        app.log.info({ level: dbLogLevel }, 'Log level loaded from config')
      }
    } catch (err) {
      app.log.error({ err }, 'Failed to load app config')
    }
    try {
      await seedInitialUser(app)
    } catch (err) {
      app.log.error({ err }, 'Failed to seed initial user')
    }
    try {
      await seedEmailSettings(app)
    } catch (err) {
      app.log.error({ err }, 'Failed to seed email settings')
    }
    try {
      await seedCompany(app)
    } catch (err) {
      app.log.error({ err }, 'Failed to seed company')
    }
  })

  return app
}

async function runMigrations() {
  const connectionString = process.env.DATABASE_URL
  if (!connectionString) throw new Error('DATABASE_URL is required')

  const migrationsFolder = process.env.MIGRATIONS_DIR ?? './src/db/migrations'
  const sql = postgres(connectionString, { max: 1 })
  const db = drizzle(sql)

  await migrate(db, { migrationsFolder })
  await sql.end()
}

async function start() {
  await runMigrations()
  const app = await buildApp()

  const port = parseInt(process.env.PORT ?? '8000', 10)
  const host = process.env.HOST ?? '0.0.0.0'

  try {
    await app.listen({ port, host })
  } catch (err) {
    app.log.error(err)
    process.exit(1)
  }
}

// Only auto-start when not imported by tests
if (process.env.NODE_ENV !== 'test') {
  start()
}
