import { describe, it, expect } from 'vitest'
import {
  assertSafeOutboundUrl,
  assertSafePushEndpointUrl,
  UnsafeOutboundUrlError,
  validateSafeOutboundUrl,
  validateSafePushEndpointUrl,
} from '../src/ssrf-guard'

describe('validateSafeOutboundUrl', () => {
  const blockedUrls = [
    'http://hooks.example.com/hook',
    'https://localhost/hook',
    'https://app.localhost/hook',
    'https://metadata.google.internal/computeMetadata/v1/',
    'https://metadata.example.com/',
    'https://service.internal/hook',
    'https://printer.local/hook',
    'https://127.0.0.1/',
    'https://127.1/',
    'https://10.0.0.1/',
    'https://172.16.0.1/',
    'https://172.31.255.255/',
    'https://192.168.0.1/',
    'https://169.254.169.254/latest/meta-data/',
    'https://0.0.0.0/',
    'https://0.1.2.3/',
    'https://2130706433/',
    'https://0177.0.0.1/',
    'https://0x7f000001/',
    'https://[::1]/',
    'https://[fc00::1]/',
    'https://[fe80::1]/',
    'https://[::ffff:127.0.0.1]/',
    'https://[0:0:0:0:0:ffff:127.0.0.1]/',
  ]

  it.each(blockedUrls)('rejects blocked URL %s', (url) => {
    expect(validateSafeOutboundUrl(url)).toEqual({
      ok: false,
      reason: 'URL must be a public HTTPS endpoint',
    })
    expect(() => assertSafeOutboundUrl(url)).toThrow(UnsafeOutboundUrlError)
  })

  const allowedUrls = [
    'https://hooks.zapier.com/hooks/123',
    'https://example.com/webhook',
    'https://api.github.com/repos/org/repo/hooks/1',
    'https://my-company.atlassian.net/rest/api/3/search',
  ]

  it.each(allowedUrls)('allows public HTTPS URL %s', (url) => {
    expect(validateSafeOutboundUrl(url)).toEqual({ ok: true })
    expect(() => assertSafeOutboundUrl(url)).not.toThrow()
  })

  it('rejects trailing-dot hostname bypass attempts', () => {
    const trailingDotBlocked = [
      'https://169.254.169.254./latest/meta-data/',
      'https://metadata.google.internal./computeMetadata/v1/',
    ]
    for (const url of trailingDotBlocked) {
      expect(validateSafeOutboundUrl(url)).toEqual({
        ok: false,
        reason: 'URL must be a public HTTPS endpoint',
      })
    }
  })

  it('allows normal hosts with or without trailing dot on public domains', () => {
    expect(validateSafeOutboundUrl('https://example.com/webhook')).toEqual({ ok: true })
    expect(validateSafeOutboundUrl('https://example.com./webhook')).toEqual({ ok: true })
    expect(validateSafeOutboundUrl('https://hooks.zapier.com/hooks/123')).toEqual({ ok: true })
  })
})

describe('webhook PATCH SSRF guard', () => {
  it('rejects private IP when url is updated', () => {
    const updateData = { url: 'https://127.0.0.1/' }
    if (updateData.url) {
      expect(() => assertSafeOutboundUrl(updateData.url)).toThrow(UnsafeOutboundUrlError)
    }
  })

  it('allows public URL when url is updated', () => {
    const updateData = { url: 'https://hooks.zapier.com/hooks/123' }
    if (updateData.url) {
      expect(() => assertSafeOutboundUrl(updateData.url)).not.toThrow()
    }
  })
})

describe('validateSafePushEndpointUrl', () => {
  it('allows known push service endpoints', () => {
    expect(
      validateSafePushEndpointUrl('https://fcm.googleapis.com/fcm/send/abc'),
    ).toEqual({ ok: true })
    expect(
      validateSafePushEndpointUrl('https://updates.push.services.mozilla.com/wpush/v2/gAAAAA'),
    ).toEqual({ ok: true })
    expect(() =>
      assertSafePushEndpointUrl('https://notify.windows.com/w/?token=abc'),
    ).not.toThrow()
  })

  it('rejects non-push hosts even when public', () => {
    expect(validateSafePushEndpointUrl('https://hooks.zapier.com/hooks/123')).toEqual({
      ok: false,
      reason: 'Push endpoint must be a known public HTTPS push service URL',
    })
  })

  it('rejects private push-looking hosts', () => {
    expect(validateSafePushEndpointUrl('https://127.0.0.1:9999/ssrf')).toEqual({
      ok: false,
      reason: 'Push endpoint must be a known public HTTPS push service URL',
    })
  })
})
