import http from 'node:http'
import { URL } from 'node:url'
import type { GitHubConfig } from '../types'
import {
  dispatchGitHubWebhook,
  type GitHubWebhookAction,
  verifyGitHubWebhookSignature,
} from './githubWebhook'

const DEFAULT_WEBHOOK_PATH = '/github/webhook'
const MAX_BODY_BYTES = 1024 * 1024

export interface GitHubWebhookReceiverOptions {
  secret: string
  path?: string
  maxBodyBytes?: number
  onAction?: (action: GitHubWebhookAction, event: string, payload: unknown) => void | Promise<void>
}

export function createGitHubWebhookServer(
  options: GitHubWebhookReceiverOptions,
): http.Server {
  const webhookPath = options.path ?? DEFAULT_WEBHOOK_PATH
  const maxBodyBytes = options.maxBodyBytes ?? MAX_BODY_BYTES

  return http.createServer(async (req, res) => {
    try {
      const requestUrl = new URL(req.url ?? '/', 'http://localhost')
      if (req.method !== 'POST' || requestUrl.pathname !== webhookPath) {
        sendJson(res, 404, { ok: false, error: 'not_found' })
        return
      }

      const rawBody = await readRawBody(req, maxBodyBytes)
      const signature = headerValue(req.headers['x-hub-signature-256'])
      if (!verifyGitHubWebhookSignature(rawBody, signature, options.secret)) {
        sendJson(res, 401, { ok: false, error: 'invalid_signature' })
        return
      }

      const event = headerValue(req.headers['x-github-event']) ?? 'unknown'
      let payload: unknown
      try {
        payload = JSON.parse(rawBody.toString('utf8'))
      } catch {
        sendJson(res, 400, { ok: false, error: 'invalid_json' })
        return
      }

      const action = dispatchGitHubWebhook(event, payload)
      await options.onAction?.(action, event, payload)
      sendJson(res, 202, { ok: true, type: action.type })
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err)
      if (message === 'payload_too_large') {
        sendJson(res, 413, { ok: false, error: 'payload_too_large' })
        return
      }
      console.error('[github-webhook] receiver falhou:', message)
      sendJson(res, 500, { ok: false, error: 'internal_error' })
    }
  })
}

export function startGitHubWebhookReceiver(
  config: GitHubConfig | undefined,
  onAction?: GitHubWebhookReceiverOptions['onAction'],
): http.Server | undefined {
  if (!config) return undefined

  const envPort = process.env['SDD_GITHUB_WEBHOOK_PORT']
  const configuredPort = config.webhook?.port
  const enabled = config.webhook?.enabled === true || configuredPort != null || envPort != null
  if (!enabled) return undefined

  const port = Number(envPort ?? configuredPort)
  if (!Number.isFinite(port) || port <= 0) {
    console.warn('[github-webhook] receiver não iniciado: porta inválida')
    return undefined
  }

  const secretEnv = config.webhook_secret_env ?? 'GITHUB_WEBHOOK_SECRET'
  const secret = process.env[secretEnv]
  if (!secret) {
    console.warn(`[github-webhook] receiver não iniciado: variável ${secretEnv} ausente`)
    return undefined
  }

  const path = config.webhook?.path ?? DEFAULT_WEBHOOK_PATH
  const server = createGitHubWebhookServer({ secret, path, onAction })
  server.listen(port, () => {
    console.log(`[github-webhook] receiver ativo em ${path} na porta ${port}`)
  })
  return server
}

function headerValue(value: string | string[] | undefined): string | undefined {
  return Array.isArray(value) ? value[0] : value
}

function readRawBody(req: http.IncomingMessage, maxBytes: number): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = []
    let total = 0
    let settled = false

    req.on('data', (chunk: Buffer) => {
      if (settled) return
      total += chunk.length
      if (total > maxBytes) {
        settled = true
        reject(new Error('payload_too_large'))
        req.resume()
        return
      }
      chunks.push(chunk)
    })
    req.on('end', () => {
      if (settled) return
      settled = true
      resolve(Buffer.concat(chunks))
    })
    req.on('error', (err) => {
      if (settled) return
      settled = true
      reject(err)
    })
  })
}

function sendJson(res: http.ServerResponse, statusCode: number, body: unknown): void {
  res.statusCode = statusCode
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify(body))
}
