import fs from 'node:fs'
import path from 'node:path'

import { buildRuntimeReadinessReport, formatRuntimeReadinessReport } from './runtime-readiness-lib.mjs'
import {
  renderNginxConfig,
  renderSystemdService,
  resolveDeploymentRenderOptions,
} from './render-same-origin-deployment-lib.mjs'

function normalizeTrimmed(value) {
  return String(value || '').trim()
}

function normalizeBoolean(value, fallback = false) {
  if (typeof value === 'boolean') {
    return value
  }
  const normalized = normalizeTrimmed(value).toLowerCase()
  if (!normalized) {
    return fallback
  }
  return normalized === 'true' || normalized === '1' || normalized === 'yes'
}

function normalizeServerNames(serverNames) {
  const names = Array.isArray(serverNames)
    ? serverNames
    : String(serverNames || '').split(',')

  return names
    .map((value) => normalizeTrimmed(value))
    .filter(Boolean)
}

function requireNonEmpty(value, label) {
  const normalized = normalizeTrimmed(value)
  if (!normalized) {
    throw new Error(`${label} is required.`)
  }
  return normalized
}

function resolvePublicOrigin(manifest = {}, serverNames = []) {
  const explicitOrigin = normalizeTrimmed(manifest.publicOrigin || '')
  if (explicitOrigin) {
    return requireNonEmpty(explicitOrigin, 'publicOrigin')
  }

  const primaryServerName = serverNames[0] || 'hypertwist.app'
  return `https://${primaryServerName}`
}

function normalizeDeploymentTier(value, fallback = 'launch') {
  const normalized = normalizeTrimmed(value).toLowerCase()
  if (normalized === 'preview' || normalized === 'launch') {
    return normalized
  }
  return fallback
}

function requireNonEmptyForTier(value, label, deploymentTier) {
  const normalized = normalizeTrimmed(value)
  if (!normalized && deploymentTier !== 'preview') {
    throw new Error(`${label} is required.`)
  }
  return normalized
}

function quoteEnvValue(value) {
  const normalized = String(value ?? '')
  if (!normalized) {
    return ''
  }

  if (/[ \t#{}[\]"']/.test(normalized)) {
    return `'${normalized.replace(/'/g, "'\\''")}'`
  }

  return normalized
}

function serializeEnvEntries(entries) {
  return Object.entries(entries)
    .map(([key, value]) => `${key}=${quoteEnvValue(value)}`)
    .join('\n')
    .concat('\n')
}

export function loadBundleManifest(filePath) {
  const absolutePath = path.resolve(filePath)
  const raw = fs.readFileSync(absolutePath, 'utf8')
  return {
    path: absolutePath,
    manifest: JSON.parse(raw),
  }
}

export function resolveBundleManifest(manifest = {}) {
  const server = manifest.server || {}
  const windowsRelease = manifest.windowsRelease || {}
  const serviceUser = normalizeTrimmed(manifest.serviceUser || 'hypertwist')
  const serviceGroup = normalizeTrimmed(manifest.serviceGroup || serviceUser)
  const serverNames = normalizeServerNames(manifest.serverNames)
  const deploymentTier = normalizeDeploymentTier(manifest.deploymentTier || server.deploymentTier, 'launch')

  return {
    checkoutRoot: requireNonEmpty(manifest.checkoutRoot, 'checkoutRoot'),
    serviceUser,
    serviceGroup,
    deploymentTier,
    publicOrigin: resolvePublicOrigin(manifest, serverNames),
    serverNames: serverNames.length > 0 ? serverNames : undefined,
    certificateName: normalizeTrimmed(manifest.certificateName || ''),
    supportEmail: requireNonEmpty(manifest.supportEmail, 'supportEmail'),
    publicDocsUrl: requireNonEmpty(manifest.publicDocsUrl, 'publicDocsUrl'),
    releaseNotesUrl: requireNonEmpty(manifest.releaseNotesUrl, 'releaseNotesUrl'),
    correspondingSourceUrl: requireNonEmpty(manifest.correspondingSourceUrl, 'correspondingSourceUrl'),
    openSourceRepoUrl: requireNonEmpty(manifest.openSourceRepoUrl, 'openSourceRepoUrl'),
    operatorCheckoutUrl: requireNonEmptyForTier(manifest.operatorCheckoutUrl, 'operatorCheckoutUrl', deploymentTier),
    studioCheckoutUrl: normalizeTrimmed(manifest.studioCheckoutUrl || ''),
    planPriceOperator: normalizeTrimmed(manifest.planPriceOperator || 'Launch pricing via Paddle'),
    planPriceStudio: normalizeTrimmed(manifest.planPriceStudio || 'Contact for launch readiness'),
    windowsDownloadUrl: requireNonEmptyForTier(manifest.windowsDownloadUrl, 'windowsDownloadUrl', deploymentTier),
    releaseManifestVersion: requireNonEmptyForTier(manifest.releaseManifestVersion, 'releaseManifestVersion', deploymentTier),
    releaseManifestChannel: normalizeTrimmed(manifest.releaseManifestChannel || 'preview'),
    windowsRelease: {
      buildId: requireNonEmptyForTier(windowsRelease.buildId, 'windowsRelease.buildId', deploymentTier),
      sha256: requireNonEmptyForTier(windowsRelease.sha256, 'windowsRelease.sha256', deploymentTier),
      fileSizeBytes: String(windowsRelease.fileSizeBytes ?? ''),
      publishedAt: requireNonEmptyForTier(windowsRelease.publishedAt, 'windowsRelease.publishedAt', deploymentTier),
    },
    server: {
      port: normalizeTrimmed(server.port || '3011'),
      deploymentTier,
      superTokensCoreUri: normalizeTrimmed(server.superTokensCoreUri || 'http://127.0.0.1:3567'),
      apiBasePath: normalizeTrimmed(server.apiBasePath || '/auth'),
      websiteBasePath: normalizeTrimmed(server.websiteBasePath || '/auth'),
      cookieSecure: normalizeBoolean(server.cookieSecure, true),
      serveStaticWebsite: normalizeBoolean(server.serveStaticWebsite, true),
      websiteDistPath: normalizeTrimmed(server.websiteDistPath || ''),
      githubClientId: normalizeTrimmed(server.githubClientId || ''),
      githubClientSecret: normalizeTrimmed(server.githubClientSecret || ''),
      googleClientId: normalizeTrimmed(server.googleClientId || ''),
      googleClientSecret: normalizeTrimmed(server.googleClientSecret || ''),
      orcidClientId: normalizeTrimmed(server.orcidClientId || ''),
      orcidClientSecret: normalizeTrimmed(server.orcidClientSecret || ''),
      orcidIsSandbox: normalizeBoolean(server.orcidIsSandbox, false),
      defaultPlan: normalizeTrimmed(server.defaultPlan || 'free'),
      defaultRole: normalizeTrimmed(server.defaultRole || 'operator'),
      healthTimeoutMs: normalizeTrimmed(server.healthTimeoutMs || '2000'),
      desktopLinkRateLimitMax: normalizeTrimmed(server.desktopLinkRateLimitMax || '5'),
      desktopLinkRateLimitWindowMs: normalizeTrimmed(server.desktopLinkRateLimitWindowMs || '900000'),
      paddleWebhookSecret: requireNonEmptyForTier(server.paddleWebhookSecret, 'server.paddleWebhookSecret', deploymentTier),
      paddleWebhookToleranceMs: normalizeTrimmed(server.paddleWebhookToleranceMs || '5000'),
      billingStatePath: normalizeTrimmed(server.billingStatePath || '/var/lib/hypertwist/auth/hypertwist-billing-state.json'),
      paddleProductPlanMap: server.paddleProductPlanMap || {},
      paddlePricePlanMap: server.paddlePricePlanMap || {},
    },
  }
}

export function buildFrontendEnvEntries(manifest = {}) {
  const resolved = resolveBundleManifest(manifest)

  return {
    VITE_PUBLIC_DEPLOYMENT_TIER: resolved.deploymentTier,
    VITE_SUPERTOKENS_API_DOMAIN: resolved.publicOrigin,
    VITE_SUPERTOKENS_WEBSITE_DOMAIN: resolved.publicOrigin,
    VITE_SUPERTOKENS_API_BASE_PATH: resolved.server.apiBasePath,
    VITE_SUPERTOKENS_WEBSITE_BASE_PATH: resolved.server.websiteBasePath,
    VITE_AUTH_API_BASE_URL: resolved.publicOrigin,
    VITE_AUTH_API_TIMEOUT_MS: '8000',
    VITE_GITHUB_OAUTH_ENABLED: String(Boolean(resolved.server.githubClientId && resolved.server.githubClientSecret)),
    VITE_GOOGLE_OAUTH_ENABLED: String(Boolean(resolved.server.googleClientId && resolved.server.googleClientSecret)),
    VITE_ORCID_OAUTH_ENABLED: String(Boolean(resolved.server.orcidClientId && resolved.server.orcidClientSecret)),
    VITE_PUBLIC_DOCS_URL: resolved.publicDocsUrl,
    VITE_RELEASE_NOTES_URL: resolved.releaseNotesUrl,
    VITE_SUPPORT_EMAIL: resolved.supportEmail,
    VITE_WINDOWS_DOWNLOAD_URL: resolved.windowsDownloadUrl,
    VITE_MAC_DOWNLOAD_URL: '',
    VITE_LINUX_DOWNLOAD_URL: '',
    VITE_PADDLE_CHECKOUT_URL_OPERATOR: resolved.operatorCheckoutUrl,
    VITE_PADDLE_CHECKOUT_URL_STUDIO: resolved.studioCheckoutUrl,
    VITE_PLAN_PRICE_OPERATOR: resolved.planPriceOperator,
    VITE_PLAN_PRICE_STUDIO: resolved.planPriceStudio,
    VITE_MPL_SOURCE_URL: resolved.correspondingSourceUrl,
    VITE_OPEN_SOURCE_REPO_URL: resolved.openSourceRepoUrl,
  }
}

export function buildServerEnvEntries(manifest = {}) {
  const resolved = resolveBundleManifest(manifest)

  return {
    DEPLOYMENT_TIER: resolved.deploymentTier,
    PORT: resolved.server.port,
    SUPERTOKENS_CORE_URI: resolved.server.superTokensCoreUri,
    API_DOMAIN: resolved.publicOrigin,
    WEBSITE_DOMAIN: resolved.publicOrigin,
    API_BASE_PATH: resolved.server.apiBasePath,
    WEBSITE_BASE_PATH: resolved.server.websiteBasePath,
    COOKIE_SECURE: String(resolved.server.cookieSecure),
    SERVE_STATIC_WEBSITE: String(resolved.server.serveStaticWebsite),
    WEBSITE_DIST_PATH: resolved.server.websiteDistPath,
    GITHUB_CLIENT_ID: resolved.server.githubClientId,
    GITHUB_CLIENT_SECRET: resolved.server.githubClientSecret,
    GOOGLE_CLIENT_ID: resolved.server.googleClientId,
    GOOGLE_CLIENT_SECRET: resolved.server.googleClientSecret,
    ORCID_CLIENT_ID: resolved.server.orcidClientId,
    ORCID_CLIENT_SECRET: resolved.server.orcidClientSecret,
    ORCID_IS_SANDBOX: String(resolved.server.orcidIsSandbox),
    DEFAULT_PLAN: resolved.server.defaultPlan,
    DEFAULT_ROLE: resolved.server.defaultRole,
    SUPERTOKENS_HEALTH_TIMEOUT_MS: resolved.server.healthTimeoutMs,
    DESKTOP_LINK_RATE_LIMIT_MAX: resolved.server.desktopLinkRateLimitMax,
    DESKTOP_LINK_RATE_LIMIT_WINDOW_MS: resolved.server.desktopLinkRateLimitWindowMs,
    PADDLE_WEBHOOK_SECRET: resolved.server.paddleWebhookSecret,
    PADDLE_WEBHOOK_TOLERANCE_MS: resolved.server.paddleWebhookToleranceMs,
    BILLING_STATE_PATH: resolved.server.billingStatePath,
    PADDLE_PRODUCT_PLAN_MAP: JSON.stringify(resolved.server.paddleProductPlanMap),
    PADDLE_PRICE_PLAN_MAP: JSON.stringify(resolved.server.paddlePricePlanMap),
    SUPPORT_EMAIL: resolved.supportEmail,
    PUBLIC_DOCS_URL: resolved.publicDocsUrl,
    RELEASE_NOTES_URL: resolved.releaseNotesUrl,
    MPL_SOURCE_URL: resolved.correspondingSourceUrl,
    OPEN_SOURCE_REPO_URL: resolved.openSourceRepoUrl,
    RELEASE_MANIFEST_VERSION: resolved.releaseManifestVersion,
    RELEASE_MANIFEST_CHANNEL: resolved.releaseManifestChannel,
    WINDOWS_DOWNLOAD_URL: resolved.windowsDownloadUrl,
    WINDOWS_RELEASE_BUILD_ID: resolved.windowsRelease.buildId,
    WINDOWS_RELEASE_SHA256: resolved.windowsRelease.sha256,
    WINDOWS_RELEASE_FILE_SIZE_BYTES: resolved.windowsRelease.fileSizeBytes,
    WINDOWS_RELEASE_PUBLISHED_AT: resolved.windowsRelease.publishedAt,
    MACOS_DOWNLOAD_URL: '',
    MACOS_RELEASE_BUILD_ID: '',
    MACOS_RELEASE_SHA256: '',
    MACOS_RELEASE_FILE_SIZE_BYTES: '',
    MACOS_RELEASE_PUBLISHED_AT: '',
    LINUX_DOWNLOAD_URL: '',
    LINUX_RELEASE_BUILD_ID: '',
    LINUX_RELEASE_SHA256: '',
    LINUX_RELEASE_FILE_SIZE_BYTES: '',
    LINUX_RELEASE_PUBLISHED_AT: '',
  }
}

export function buildBundleValidationReport(manifest = {}) {
  const resolved = resolveBundleManifest(manifest)
  const frontendEnv = buildFrontendEnvEntries(resolved)
  const serverEnv = buildServerEnvEntries(resolved)

  return buildRuntimeReadinessReport({
    frontendEnv,
    serverEnv,
    deploymentTier: resolved.deploymentTier,
    liveHealth: null,
    liveHealthAttempted: false,
    liveReleaseManifest: null,
    liveReleaseManifestAttempted: false,
    liveWebsiteShell: null,
    liveWebsiteShellAttempted: false,
  })
}

export function renderSameOriginBundle(manifest = {}) {
  const resolved = resolveBundleManifest(manifest)
  const frontendEnv = buildFrontendEnvEntries(resolved)
  const serverEnv = buildServerEnvEntries(resolved)
  const deploymentOptions = resolveDeploymentRenderOptions({
    checkoutRoot: resolved.checkoutRoot,
    serviceUser: resolved.serviceUser,
    serviceGroup: resolved.serviceGroup,
    serverNames: resolved.serverNames,
    certificateName: resolved.certificateName,
    upstreamPort: resolved.server.port,
  })

  const validationReport = buildRuntimeReadinessReport({
    frontendEnv,
    serverEnv,
    deploymentTier: resolved.deploymentTier,
    liveHealth: null,
    liveHealthAttempted: false,
    liveReleaseManifest: null,
    liveReleaseManifestAttempted: false,
    liveWebsiteShell: null,
    liveWebsiteShellAttempted: false,
  })

  if (!validationReport.ok) {
    throw new Error(`Rendered deployment bundle is not runtime-ready.\n${formatRuntimeReadinessReport(validationReport)}`)
  }

  return {
    deploymentOptions,
    frontendEnv,
    serverEnv,
    frontendEnvContent: serializeEnvEntries(frontendEnv),
    serverEnvContent: serializeEnvEntries(serverEnv),
    systemdContent: renderSystemdService(deploymentOptions),
    nginxContent: renderNginxConfig(deploymentOptions),
    validationReport,
  }
}

export function writeSameOriginBundle({
  manifest,
  outDir,
}) {
  const rendered = renderSameOriginBundle(manifest)
  const outputDir = path.resolve(outDir)

  fs.mkdirSync(outputDir, { recursive: true })

  const frontendEnvPath = path.join(outputDir, 'website.env')
  const serverEnvPath = path.join(outputDir, 'server.env')
  const systemdPath = path.join(outputDir, 'hypertwist-website-auth-server.service')
  const nginxPath = path.join(outputDir, 'hypertwist.app.conf')
  const summaryPath = path.join(outputDir, 'bundle-summary.json')

  fs.writeFileSync(frontendEnvPath, rendered.frontendEnvContent, 'utf8')
  fs.writeFileSync(serverEnvPath, rendered.serverEnvContent, 'utf8')
  fs.writeFileSync(systemdPath, rendered.systemdContent, 'utf8')
  fs.writeFileSync(nginxPath, rendered.nginxContent, 'utf8')
  fs.writeFileSync(summaryPath, `${JSON.stringify({
    frontendEnvPath,
    serverEnvPath,
    systemdPath,
    nginxPath,
    validationReport: rendered.validationReport,
  }, null, 2)}\n`, 'utf8')

  return {
    ...rendered,
    outputDir,
    frontendEnvPath,
    serverEnvPath,
    systemdPath,
    nginxPath,
    summaryPath,
  }
}
