// `safeOpenStatusOriginal` — CVE-2024-23832-class belt-and-braces test (P2b).
// Pixelfed/Mastodon Status objects carry both `url` (home-server's rendered
// HTML page) and `uri` (canonical ActivityPub `id`). They MUST resolve to
// the same hostname; a remote-stamped Status with `id = https://victim/...`
// served by a not-yet-patched home server would otherwise let "view
// original" deep-link the user to the spoofed host under the impersonated
// identity.
//
// Pins:
//   1. Same-host (url and uri agree) → opens.
//   2. Cross-host (url and uri hostnames differ) → silent drop.
//   3. `url` only (uri null) → opens (no comparison possible — falls
//      through to openBrowserAsync's https:// allowlist).
//   4. `uri` only (url null) → opens via uri.
//   5. Both null → no-op.
//   6. Null/undefined status → no-op.
//   7. Malformed url or uri → silent drop.
//   8. Case-insensitive hostname match (HOST.example vs host.example).
//   9. https vs http on same hostname → still opens (the gate is
//      hostname-only; the underlying openBrowserAsync allowlist already
//      pins the scheme to https? — pinning the cross-protocol case here
//      keeps the helper's contract narrow and reuses the lower layer).

const mockOpenBrowserAsync = jest.fn<Promise<unknown>, [string, unknown?]>(
  async () => undefined
)
const mockOpenURL = jest.fn<Promise<unknown>, [string]>(async () => undefined)

jest.mock('expo-web-browser', () => ({
  openBrowserAsync: (url: string, opts?: unknown) => mockOpenBrowserAsync(url, opts),
}))

jest.mock('expo-linking', () => ({
  openURL: (url: string) => mockOpenURL(url),
}))

type UtilsModule = typeof import('../src/utils')

let safeOpenStatusOriginal: UtilsModule['safeOpenStatusOriginal']

beforeAll(() => {
  const utils = require('../src/utils') as UtilsModule
  safeOpenStatusOriginal = utils.safeOpenStatusOriginal
})

// The cross-host branch fires a `__DEV__`-gated `console.warn`. Silence
// it in this suite so the noise doesn't drown out genuine future warnings.
let warnSpy: jest.SpyInstance

beforeAll(() => {
  warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
})

afterAll(() => {
  warnSpy.mockRestore()
})

beforeEach(() => {
  mockOpenBrowserAsync.mockClear()
  mockOpenURL.mockClear()
})

// Default `ui.openLinks=external` routes opens through `Linking.openURL`
// (mocked as `mockOpenURL`); the MMKV pref isn't loadable under jest-expo
// so the resolver's fallback returns `'external'`. Pin both mocks where
// the assertion is "did anything open at all" vs "was the gate refused".

describe('safeOpenStatusOriginal — host contract', () => {
  it('opens when url and uri share the same hostname', async () => {
    await safeOpenStatusOriginal({
      url: 'https://pixelfed.social/p/alice/123',
      uri: 'https://pixelfed.social/p/alice/123',
    })
    expect(mockOpenURL).toHaveBeenCalledTimes(1)
    expect(mockOpenURL).toHaveBeenCalledWith('https://pixelfed.social/p/alice/123')
  })

  it('drops when url and uri have different hostnames', async () => {
    await safeOpenStatusOriginal({
      url: 'https://pixelfed.social/p/alice/123',
      uri: 'https://victim.example/posts/X',
    })
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('opens via url when uri is null', async () => {
    await safeOpenStatusOriginal({
      url: 'https://pixelfed.social/p/alice/123',
      uri: null as unknown as string,
    })
    expect(mockOpenURL).toHaveBeenCalledTimes(1)
  })

  it('opens via uri when url is null', async () => {
    await safeOpenStatusOriginal({
      url: null,
      uri: 'https://pixelfed.social/p/alice/123',
    })
    expect(mockOpenURL).toHaveBeenCalledTimes(1)
    expect(mockOpenURL).toHaveBeenCalledWith('https://pixelfed.social/p/alice/123')
  })

  it('no-ops when both url and uri are null/empty', async () => {
    await safeOpenStatusOriginal({ url: null, uri: '' })
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('no-ops on null status', async () => {
    await safeOpenStatusOriginal(null)
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('no-ops on undefined status', async () => {
    await safeOpenStatusOriginal(undefined)
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('drops when url is malformed', async () => {
    await safeOpenStatusOriginal({
      url: 'not a url',
      uri: 'https://pixelfed.social/p/alice/123',
    })
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('drops when uri is malformed', async () => {
    await safeOpenStatusOriginal({
      url: 'https://pixelfed.social/p/alice/123',
      uri: 'not a url',
    })
    expect(mockOpenBrowserAsync).not.toHaveBeenCalled()
    expect(mockOpenURL).not.toHaveBeenCalled()
  })

  it('matches hostnames case-insensitively', async () => {
    await safeOpenStatusOriginal({
      url: 'https://PixelFed.Social/p/alice/123',
      uri: 'https://pixelfed.social/p/alice/123',
    })
    expect(mockOpenURL).toHaveBeenCalledTimes(1)
  })

  it('does not require url and uri to share a scheme (host-only contract)', async () => {
    // The cross-protocol case isn't a forgery primitive — it's the same
    // host, so the user lands on the same server. The lower-layer
    // openBrowserAsync allowlist independently enforces https?: only.
    await safeOpenStatusOriginal({
      url: 'https://pixelfed.social/p/alice/123',
      uri: 'http://pixelfed.social/p/alice/123',
    })
    expect(mockOpenURL).toHaveBeenCalledTimes(1)
  })
})
