import {test, expect, createTestContent, createTextContent} from './fixtures'
import {UploadPage} from './pages/upload-page'
import {DownloadPage} from './pages/download-page'
import {readFileSync, statSync} from 'fs'
import {createHash} from 'crypto'

// ─────────────────────────────────────────────────────────────────────────────
// Upload Flow Tests
// ─────────────────────────────────────────────────────────────────────────────

test.describe('Upload Flow', () => {
  test('upload via file picker button', async ({uploadPage}) => {
    await uploadPage.expectDropZoneVisible()

    await uploadPage.selectTextFile('picker-test.txt', 'test content ' + Date.now())
    // Small files (< 100KB) skip "Encrypting" and go directly to "Uploading"
    // await uploadPage.waitForEncrypting()
    await uploadPage.waitForUploading()

    const link = await uploadPage.waitForShareLink()
    expect(link).toMatch(/^http:\/\/localhost:\d+\/?#/)
  })

  test('upload via file input (drag-drop code path)', async ({uploadPage}) => {
    // Tests file handling logic - the drop handler uses the same input processing
    await uploadPage.dragDropFile('dragdrop-test.txt', createTextContent('drag drop test'))
    await uploadPage.expectProgressVisible()

    const link = await uploadPage.waitForShareLink()
    expect(link).toContain('#')
  })

  test('upload rejects file over 100MB', async ({uploadPage}) => {
    await uploadPage.selectLargeFile('large.bin', 100 * 1024 * 1024 + 1)
    await uploadPage.expectError('too large')
    await uploadPage.expectError('100 MB')
  })

  test('upload rejects empty file', async ({uploadPage}) => {
    await uploadPage.selectFile('empty.txt', Buffer.alloc(0))
    await uploadPage.expectError('empty')
  })

  test('upload shows progress during encryption and upload', async ({uploadPage}) => {
    await uploadPage.selectFile('progress-test.bin', createTestContent(500 * 1024))

    await uploadPage.expectProgressVisible()
    await uploadPage.waitForEncrypting()
    await uploadPage.waitForUploading()
    await uploadPage.waitForShareLink()
  })

  test('cancel button aborts upload and returns to landing', async ({uploadPage}) => {
    await uploadPage.selectFile('cancel-test.bin', createTestContent(1024 * 1024))
    await uploadPage.expectProgressVisible()

    await uploadPage.clickCancel()

    await uploadPage.expectDropZoneVisible()
    await uploadPage.expectShareLinkNotVisible()
  })

  test('share link copy button works', async ({uploadPage, context}) => {
    await context.grantPermissions(['clipboard-read', 'clipboard-write'])

    await uploadPage.selectTextFile('copy-test.txt', 'copy test content')
    const link = await uploadPage.waitForShareLink()

    await uploadPage.clickCopy()

    // Verify clipboard (may fail in headless)
    try {
      const clipboardText = await uploadPage.page.evaluate(() => navigator.clipboard.readText())
      expect(clipboardText).toBe(link)
    } catch {
      // Clipboard API may not be available in headless mode
    }
  })

  test('error state shows retry button', async ({uploadPage}) => {
    await uploadPage.selectFile('error-test.txt', Buffer.alloc(0))
    await uploadPage.expectError('empty')
    await uploadPage.expectRetryButtonVisible()
  })

  test('upload complete shows expiry and security note', async ({uploadPage}) => {
    await uploadPage.selectTextFile('ui-test.txt', 'ui test')
    await uploadPage.waitForShareLink()

    await uploadPage.expectCompleteWithExpiry()
    await uploadPage.expectSecurityNote()
  })
})

// ─────────────────────────────────────────────────────────────────────────────
// Download Flow Tests
// ─────────────────────────────────────────────────────────────────────────────

test.describe('Download Flow', () => {
  test('non-XFTP anchor hash shows upload page', async ({page}) => {
    await page.goto('http://localhost:4173#hello-world')
    await expect(page.locator('#drop-zone')).toBeVisible()
  })

  test('short non-XFTP hash shows upload page', async ({page}) => {
    await page.goto('http://localhost:4173#AAAA')
    await expect(page.locator('#drop-zone')).toBeVisible()
  })

  test('corrupted XFTP hash shows error', async ({downloadPage}) => {
    // Long base64url string that looks like XFTP but is not valid DEFLATE data
    const corrupted = 'A'.repeat(100)
    await downloadPage.goto('#' + corrupted)
    await downloadPage.expectInitialError(/[Ii]nvalid|corrupted/)
  })

  test('download button initiates download', async ({uploadPage, downloadPage}) => {
    await uploadPage.selectTextFile('dl-btn-test.txt', 'download test content')
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    await downloadPage.expectFileReady()

    const download = await downloadPage.clickDownload()
    expect(download.suggestedFilename()).toBe('dl-btn-test.txt')
  })

  test('download shows progress', async ({uploadPage, downloadPage}) => {
    // Use larger file to ensure progress is observable
    await uploadPage.selectFile('dl-progress.bin', createTestContent(1024 * 1024))
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)

    // Set up download listener before clicking
    const downloadPromise = downloadPage.page.waitForEvent('download')

    // Click starts download - progress should be visible while downloading
    await downloadPage.startDownload()
    await downloadPage.expectProgressVisible()

    await downloadPromise
  })

  test('downloaded file content matches upload', async ({uploadPage, downloadPage}) => {
    const content = 'verification content ' + Date.now()
    const fileName = 'verify.txt'

    await uploadPage.selectTextFile(fileName, content)
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    expect(download.suggestedFilename()).toBe(fileName)

    const path = await download.path()
    if (path) {
      const downloadedContent = readFileSync(path, 'utf-8')
      expect(downloadedContent).toBe(content)
    }
  })

  test('download page shows file size and security note', async ({uploadPage, downloadPage}) => {
    await uploadPage.selectFile('size-test.bin', createTestContent(1024))
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    await downloadPage.expectFileSizeDisplayed()
    await downloadPage.expectSecurityNote()
  })
})

// ─────────────────────────────────────────────────────────────────────────────
// Edge Cases
// ─────────────────────────────────────────────────────────────────────────────

test.describe('Edge Cases', () => {
  test('upload and download 1-byte file', async ({uploadPage, downloadPage}) => {
    await uploadPage.selectFile('tiny.bin', Buffer.from([0x42]))
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    expect(download.suggestedFilename()).toBe('tiny.bin')

    const path = await download.path()
    if (path) {
      const content = readFileSync(path)
      expect(content.length).toBe(1)
      expect(content[0]).toBe(0x42)
    }
  })

  test('upload and download file with unicode filename', async ({uploadPage, downloadPage}) => {
    const fileName = 'test-\u4e2d\u6587-\u0420\u0443\u0441\u0441\u043a\u0438\u0439.txt'

    await uploadPage.selectTextFile(fileName, 'unicode filename test')
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    // Browser download attribute uses encodeURIComponent for non-ASCII filenames
    expect(download.suggestedFilename()).toBe(encodeURIComponent(fileName))
  })

  test('upload and download file with spaces', async ({uploadPage, downloadPage}) => {
    const fileName = 'my document (final) v2.txt'

    await uploadPage.selectTextFile(fileName, 'spaces test')
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    // Browser download attribute uses encodeURIComponent for the filename
    expect(download.suggestedFilename()).toBe(encodeURIComponent(fileName))
  })

  test('filename with path separators is sanitized', async ({uploadPage, downloadPage}) => {
    await uploadPage.selectTextFile('../../../etc/passwd', 'path traversal test')
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    expect(download.suggestedFilename()).not.toContain('/')
    expect(download.suggestedFilename()).not.toContain('\\')
  })

  test('binary file with all byte values', async ({uploadPage, downloadPage}) => {
    // Create buffer with all 256 byte values
    const buffer = Buffer.alloc(256)
    for (let i = 0; i < 256; i++) buffer[i] = i

    await uploadPage.selectFile('all-bytes.bin', buffer)
    const link = await uploadPage.waitForShareLink()

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    const path = await download.path()
    if (path) {
      const content = readFileSync(path)
      expect(content.length).toBe(256)
      for (let i = 0; i < 256; i++) {
        expect(content[i]).toBe(i)
      }
    }
  })

  test('upload handles network error gracefully', async ({uploadPage}) => {
    // Intercept and abort server requests after encryption starts
    await uploadPage.page.route('**/*', route => {
      const url = route.request().url()
      // Only abort XFTP server requests (HTTPS), not the web page (HTTP)
      if (url.startsWith('https://') && route.request().method() !== 'GET') {
        route.abort('failed')
      } else {
        route.continue()
      }
    })

    await uploadPage.selectTextFile('network-error.txt', 'network error test')
    await uploadPage.expectError(/.+/) // Any error message
  })

  test('concurrent downloads from same link', async ({browser}) => {
    const context = await browser.newContext({ignoreHTTPSErrors: true})
    const page1 = await context.newPage()
    const upload = new UploadPage(page1)

    await upload.goto()
    await upload.selectTextFile('concurrent.txt', 'concurrent download test')
    const link = await upload.waitForShareLink()
    const hash = upload.getHashFromLink(link)

    // Open two tabs and download concurrently (shared HTTP/2 connection)
    const page2 = await context.newPage()
    const page3 = await context.newPage()
    const dl2 = new DownloadPage(page2)
    const dl3 = new DownloadPage(page3)

    await dl2.goto(hash)
    await dl3.goto(hash)

    const [download2, download3] = await Promise.all([
      dl2.clickDownload(),
      dl3.clickDownload()
    ])

    expect(download2.suggestedFilename()).toBe('concurrent.txt')
    expect(download3.suggestedFilename()).toBe('concurrent.txt')

    await context.close()
  })
})

// ─────────────────────────────────────────────────────────────────────────────
// Slow Tests (Large Files)
// ─────────────────────────────────────────────────────────────────────────────

test.describe('Slow Tests', () => {
  test('upload file at exactly 100MB', async ({uploadPage}) => {
    test.slow()
    await uploadPage.selectLargeFile('exactly-100mb.bin', 100 * 1024 * 1024)

    // Should succeed (not show error)
    await uploadPage.expectNoError()
    await uploadPage.expectProgressVisible()

    // Wait for completion (may take a while)
    await uploadPage.waitForShareLink(300_000)
  })

  test('upload and download multi-chunk file with redirect', async ({uploadPage, downloadPage}) => {
    test.slow()
    // Use ~5MB file to get multiple chunks
    await uploadPage.selectLargeFile('multi-chunk.bin', 5 * 1024 * 1024)
    const link = await uploadPage.waitForShareLink(120_000)

    await downloadPage.gotoWithLink(link)
    const download = await downloadPage.clickDownload()

    expect(download.suggestedFilename()).toBe('multi-chunk.bin')

    const path = await download.path()
    if (path) {
      const stat = statSync(path)
      expect(stat.size).toBe(5 * 1024 * 1024)
    }
  })

  // Disabled by default — run with: npx playwright test -g "100MB"
  test.skip('upload and download 100MB random file', async ({browser}) => {
    test.setTimeout(600_000) // 10 minutes

    const SIZE = 100 * 1024 * 1024

    const context = await browser.newContext({ignoreHTTPSErrors: true})
    const page = await context.newPage()
    const upload = new UploadPage(page)
    await upload.goto()

    // Generate random content in browser and compute SHA-256
    const uploadHash = await page.evaluate(async (size: number) => {
      const buffer = new ArrayBuffer(size)
      const view = new Uint8Array(buffer)
      for (let off = 0; off < size; off += 65536) {
        crypto.getRandomValues(view.subarray(off, Math.min(off + 65536, size)))
      }
      const hashBuf = await crypto.subtle.digest('SHA-256', buffer)
      const hash = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('')
      const input = document.getElementById('file-input') as HTMLInputElement
      const file = new File([buffer], 'large-100mb.bin', {type: 'application/octet-stream'})
      const dt = new DataTransfer()
      dt.items.add(file)
      input.files = dt.files
      input.dispatchEvent(new Event('change', {bubbles: true}))
      return hash
    }, SIZE)

    const link = await upload.waitForShareLink(300_000)

    const dlPage = await context.newPage()
    const dl = new DownloadPage(dlPage)
    await dl.gotoWithLink(link)
    const download = await dl.clickDownload()

    expect(download.suggestedFilename()).toBe('large-100mb.bin')

    const path = await download.path()
    expect(path).toBeTruthy()

    const stat = statSync(path!)
    expect(stat.size).toBe(SIZE)

    const downloadHash = createHash('sha256').update(readFileSync(path!)).digest('hex')
    expect(downloadHash).toBe(uploadHash)

    await context.close()
  })
})
