import { describe, expect, it } from 'vitest'

import { buildAllowedOriginMatcher, createDesktopLinkStore } from '../security'

describe('buildAllowedOriginMatcher', () => {
  it('accepts the configured origin and same-origin referrer paths', () => {
    const matchesAllowedOrigin = buildAllowedOriginMatcher('https://hypertwist.app')

    expect(matchesAllowedOrigin('https://hypertwist.app')).toBe(true)
    expect(matchesAllowedOrigin('https://hypertwist.app/app/downloads')).toBe(true)
  })

  it('rejects prefix-spoofed or unrelated origins', () => {
    const matchesAllowedOrigin = buildAllowedOriginMatcher('https://hypertwist.app')

    expect(matchesAllowedOrigin('https://hypertwist.app.evil.example')).toBe(false)
    expect(matchesAllowedOrigin('https://evil.example/hypertwist.app')).toBe(false)
    expect(matchesAllowedOrigin('not-a-url')).toBe(false)
  })
})

describe('createDesktopLinkStore', () => {
  it('issues one-time desktop-link tokens and consumes them on verify', () => {
    let currentTime = 100
    let seed = 1
    const store = createDesktopLinkStore({
      ttlMs: 60_000,
      rateLimitMax: 5,
      rateLimitWindowMs: 60_000,
      now: () => currentTime,
      randomBytes: () => Buffer.from((seed++).toString(16).padStart(64, '0'), 'hex'),
    })

    const issue = store.issueToken({
      rateLimitKey: 'desktop-link:user-1',
      userId: 'user-1',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      accessStatus: 'active',
    })

    expect(issue.ok).toBe(true)
    if (!issue.ok) {
      return
    }

    const firstVerify = store.verifyAndConsumeToken(issue.token)
    expect(firstVerify?.email).toBe('operator@hypertwist.app')
    expect(firstVerify?.canDownload).toBe(true)
    expect(firstVerify?.accessStatus).toBe('active')
    expect(store.verifyAndConsumeToken(issue.token)).toBeNull()
    expect(store.getTokenCount()).toBe(0)
  })

  it('rate limits repeated issuance inside the configured window', () => {
    let currentTime = 1_000
    let seed = 10
    const store = createDesktopLinkStore({
      ttlMs: 60_000,
      rateLimitMax: 2,
      rateLimitWindowMs: 5_000,
      now: () => currentTime,
      randomBytes: () => Buffer.from((seed++).toString(16).padStart(64, '0'), 'hex'),
    })

    expect(store.issueToken({
      rateLimitKey: 'desktop-link:user-2',
      userId: 'user-2',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      accessStatus: 'active',
    }).ok).toBe(true)
    expect(store.issueToken({
      rateLimitKey: 'desktop-link:user-2',
      userId: 'user-2',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      accessStatus: 'active',
    }).ok).toBe(true)
    expect(store.issueToken({
      rateLimitKey: 'desktop-link:user-2',
      userId: 'user-2',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      accessStatus: 'active',
    })).toEqual({ ok: false, error: 'rate_limited' })

    currentTime += 5_001
    expect(store.issueToken({
      rateLimitKey: 'desktop-link:user-2',
      userId: 'user-2',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: true,
      accessStatus: 'active',
    }).ok).toBe(true)
  })

  it('rejects expired or malformed verification tokens', () => {
    let currentTime = 5_000
    const store = createDesktopLinkStore({
      ttlMs: 500,
      rateLimitMax: 5,
      rateLimitWindowMs: 60_000,
      now: () => currentTime,
      randomBytes: () => Buffer.from('ab'.repeat(32), 'hex'),
    })

    const issue = store.issueToken({
      rateLimitKey: 'desktop-link:user-3',
      userId: 'user-3',
      email: 'operator@hypertwist.app',
      plan: 'operator',
      role: 'operator',
      canDownload: false,
      accessStatus: 'paused',
    })

    expect(store.verifyAndConsumeToken('bad-token')).toBeNull()
    expect(issue.ok).toBe(true)
    if (!issue.ok) {
      return
    }

    currentTime += 501
    expect(store.verifyAndConsumeToken(issue.token)).toBeNull()
    expect(store.getTokenCount()).toBe(0)
  })
})
