import { describe, it, expect, vi } from 'vitest'
import { convertFileSrc } from '@tauri-apps/api/core'
import { resolveImageUrls, portableImageUrls } from './vaultImages'

let tauriMode = false

vi.mock('@tauri-apps/api/core', () => ({
  convertFileSrc: vi.fn((path: string) => `asset://localhost/${encodeURIComponent(path)}`),
}))

vi.mock('../mock-tauri', () => ({
  isTauri: () => tauriMode,
}))

function assetUrl(path: string): string {
  return `asset://localhost/${encodeURIComponent(path)}`
}

function httpAssetUrl(path: string): string {
  return `http://asset.localhost/${encodeURIComponent(path)}`
}

describe('resolveImageUrls', () => {
  it('is a no-op outside Tauri', () => {
    tauriMode = false
    const markdown = '![alt](attachments/file.png)'

    expect(resolveImageUrls(markdown, '/vault')).toBe(markdown)
  })

  it('is a no-op when vaultPath is empty', () => {
    tauriMode = true
    const markdown = '![alt](attachments/file.png)'

    expect(resolveImageUrls(markdown, '')).toBe(markdown)
  })

  it('converts relative attachment paths to asset URLs', () => {
    tauriMode = true
    const markdown = '![screenshot](attachments/1776369786040-CleanShot_2026-04-16.png)'

    expect(resolveImageUrls(markdown, '/vault')).toBe(
      `![screenshot](${assetUrl('/vault/attachments/1776369786040-CleanShot_2026-04-16.png')})`,
    )
  })

  it('converts literal-space and encoded relative attachment paths to the same asset file', () => {
    tauriMode = true

    expect(resolveImageUrls('![shot](attachments/Meeting Snapshot.png)', '/vault')).toBe(
      `![shot](${assetUrl('/vault/attachments/Meeting Snapshot.png')})`,
    )
    expect(resolveImageUrls('![shot](attachments/Meeting%20Snapshot.png)', '/vault')).toBe(
      `![shot](${assetUrl('/vault/attachments/Meeting Snapshot.png')})`,
    )
    expect(resolveImageUrls('![shot](attachments/Team%20 Sync%20 Diagram.png)', '/vault')).toBe(
      `![shot](${assetUrl('/vault/attachments/Team  Sync  Diagram.png')})`,
    )
  })

  it('converts Windows relative attachment paths without mixed separators', () => {
    tauriMode = true
    const vaultPath = 'C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started'
    const markdown = '![BlockNote image](attachments/1776508281809-CleanShot.png)'

    expect(resolveImageUrls(markdown, vaultPath)).toBe(
      `![BlockNote image](${assetUrl('C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started\\attachments\\1776508281809-CleanShot.png')})`,
    )
  })

  it('leaves already-correct asset URLs unchanged', () => {
    tauriMode = true
    const url = assetUrl('/vault/attachments/file.png')
    const markdown = `![alt](${url})`

    expect(resolveImageUrls(markdown, '/vault')).toBe(markdown)
  })

  it('rewrites legacy asset URLs from a different vault', () => {
    tauriMode = true
    const legacyUrl = assetUrl('/Users/luca/Workspace/tolaria-getting-started/attachments/CleanShot.png')
    const markdown = `![CleanShot](${legacyUrl})`

    expect(resolveImageUrls(markdown, '/Users/john/Documents/Getting Started')).toBe(
      `![CleanShot](${assetUrl('/Users/john/Documents/Getting Started/attachments/CleanShot.png')})`,
    )
  })

  it('rewrites Windows legacy asset URLs from a different vault', () => {
    tauriMode = true
    const legacyUrl = httpAssetUrl('C:\\Users\\old\\Workspace\\tolaria-getting-started\\attachments\\CleanShot.png')
    const markdown = `![CleanShot](${legacyUrl})`

    expect(resolveImageUrls(markdown, 'C:\\Users\\john\\Documents\\Getting Started')).toBe(
      `![CleanShot](${assetUrl('C:\\Users\\john\\Documents\\Getting Started\\attachments\\CleanShot.png')})`,
    )
  })

  it('leaves already-correct http asset URLs unchanged', () => {
    tauriMode = true
    const url = httpAssetUrl('/vault/attachments/file.png')
    const markdown = `![alt](${url})`

    expect(resolveImageUrls(markdown, '/vault')).toBe(markdown)
  })

  it('leaves external URLs unchanged', () => {
    tauriMode = true
    const httpImage = '![logo](https://example.com/logo.png)'
    const dataImage = '![icon](data:image/png;base64,abc123)'

    expect(resolveImageUrls(httpImage, '/vault')).toBe(httpImage)
    expect(resolveImageUrls(dataImage, '/vault')).toBe(dataImage)
  })

  it('handles multiple images in one document', () => {
    tauriMode = true
    const markdown = `![a](${assetUrl('/old/attachments/a.png')})\n\n![b](attachments/b.png)`

    const result = resolveImageUrls(markdown, '/vault')

    expect(result).toContain(`![a](${assetUrl('/vault/attachments/a.png')})`)
    expect(result).toContain(`![b](${assetUrl('/vault/attachments/b.png')})`)
  })

  it('preserves alt text and title attributes', () => {
    tauriMode = true
    const markdown = '![my screenshot](attachments/file.png "starter vault")'

    expect(resolveImageUrls(markdown, '/vault')).toBe(
      `![my screenshot](${assetUrl('/vault/attachments/file.png')} "starter vault")`,
    )
  })

  it('resolves note-relative image paths against the active note directory', () => {
    tauriMode = true
    const markdown = '![shot](./img/Meeting Snapshot.png)'

    expect(resolveImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      `![shot](${assetUrl('/vault/projects/notes/img/Meeting Snapshot.png')})`,
    )
  })

  it('resolves portable attachment image paths against the vault root from subfolder notes', () => {
    tauriMode = true
    const notePath = '/vault/projects/notes/plan.md'

    expect(resolveImageUrls('![shot](attachments/shot.png)', '/vault', notePath)).toBe(
      `![shot](${assetUrl('/vault/attachments/shot.png')})`,
    )
    expect(resolveImageUrls('![shot](./attachments/shot.png)', '/vault', notePath)).toBe(
      `![shot](${assetUrl('/vault/projects/notes/attachments/shot.png')})`,
    )
  })

  it('resolves image wikilink embeds through vault attachments', () => {
    tauriMode = true

    expect(resolveImageUrls('![[diagram.png]]', '/vault', '/vault/projects/notes/plan.md')).toBe(
      `![diagram.png](${assetUrl('/vault/attachments/diagram.png')})`,
    )
    expect(resolveImageUrls('![[attachments/diagram.png|System diagram]]', '/vault')).toBe(
      `![System diagram](${assetUrl('/vault/attachments/diagram.png')})`,
    )
    expect(resolveImageUrls('![[Project Note]]', '/vault')).toBe('![[Project Note]]')
  })

  it('resolves parenthesized and percent-encoded note-relative image paths', () => {
    tauriMode = true
    const markdown = [
      '![one](./img/foo(1).png "first")',
      '![two](./img/截图（1）.png)',
      '![three](./img/My%20File.png)',
    ].join('\n')

    expect(resolveImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe([
      `![one](${assetUrl('/vault/projects/notes/img/foo(1).png')} "first")`,
      `![two](${assetUrl('/vault/projects/notes/img/截图（1）.png')})`,
      `![three](${assetUrl('/vault/projects/notes/img/My File.png')})`,
    ].join('\n'))
  })

  it('leaves malformed parenthesized image markdown visible', () => {
    tauriMode = true
    const markdown = '![broken](attachments/foo(1.png)'

    expect(resolveImageUrls(markdown, '/vault')).toBe(markdown)
  })

  it('resolves parent traversal from the active note directory', () => {
    tauriMode = true
    const markdown = '![diagram](../shared/Architecture.png)'

    expect(resolveImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      `![diagram](${assetUrl('/vault/projects/shared/Architecture.png')})`,
    )
  })

  it('unwraps CommonMark angle-bracket image destinations before resolving paths', () => {
    tauriMode = true
    const markdown = '![diagram](<../assets/foo bar.png>)'

    expect(resolveImageUrls(markdown, '/vault', '/vault/notes/a.md')).toBe(
      `![diagram](${assetUrl('/vault/assets/foo bar.png')})`,
    )
  })

  it('preserves titles on CommonMark angle-bracket image destinations', () => {
    tauriMode = true
    const markdown = '![diagram](<../assets/foo bar.png> "diagram")'

    expect(resolveImageUrls(markdown, '/vault', '/vault/notes/a.md')).toBe(
      `![diagram](${assetUrl('/vault/assets/foo bar.png')} "diagram")`,
    )
  })

  it('keeps remote and data image URLs unchanged when notePath is present', () => {
    tauriMode = true
    const httpImage = '![logo](https://example.com/logo.png)'
    const dataImage = '![icon](data:image/png;base64,abc123)'

    expect(resolveImageUrls(httpImage, '/vault', '/vault/projects/plan.md')).toBe(httpImage)
    expect(resolveImageUrls(dataImage, '/vault', '/vault/projects/plan.md')).toBe(dataImage)
  })

  it('skips unknown asset URLs without an attachments segment', () => {
    tauriMode = true
    const url = httpAssetUrl('/some/other/path/file.png')
    const markdown = `![alt](${url})`

    expect(resolveImageUrls(markdown, '/vault')).toBe(markdown)
  })

  it('leaves image markdown intact when the native asset bridge rejects a path', () => {
    tauriMode = true
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
    vi.mocked(convertFileSrc).mockImplementationOnce(() => {
      throw new Error('null pointer passed to rust')
    })
    const markdown = '![shot](attachments/shot.png)'
    let result = ''

    try {
      expect(() => {
        result = resolveImageUrls(markdown, '/vault')
      }).not.toThrow()
      expect(result).toBe(markdown)
      expect(warnSpy).toHaveBeenCalledWith(
        '[image] Failed to prepare asset URL:',
        expect.any(Error),
      )
    } finally {
      warnSpy.mockRestore()
    }
  })
})

describe('portableImageUrls', () => {
  it('converts vault attachment asset URLs to relative paths', () => {
    const url = assetUrl('/vault/attachments/1776369786040-CleanShot.png')
    const markdown = `![screenshot](${url})`

    expect(portableImageUrls(markdown, '/vault')).toBe(
      '![screenshot](attachments/1776369786040-CleanShot.png)',
    )
  })

  it('converts legacy asset protocol attachment URLs to relative paths', () => {
    const url = httpAssetUrl('/vault/attachments/legacy.png')
    const markdown = `![screenshot](${url})`

    expect(portableImageUrls(markdown, '/vault')).toBe(
      '![screenshot](attachments/legacy.png)',
    )
  })

  it('converts Windows extended-length asset URLs to relative paths', () => {
    const url = httpAssetUrl('\\\\?\\C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started\\attachments\\1777388840027-shot.png')
    const markdown = `![screenshot](${url})`

    expect(portableImageUrls(markdown, 'C:\\Users\\lnq12\\Documents\\tolaria-test\\Getting Started')).toBe(
      '![screenshot](attachments/1777388840027-shot.png)',
    )
  })

  it('is a no-op when vaultPath is empty', () => {
    const url = assetUrl('/vault/attachments/file.png')
    const markdown = `![alt](${url})`

    expect(portableImageUrls(markdown, '')).toBe(markdown)
  })

  it('unwraps asset URLs from other vaults to absolute filesystem paths', () => {
    const url = assetUrl('/other-vault/attachments/file.png')
    const markdown = `![alt](${url})`

    expect(portableImageUrls(markdown, '/vault')).toBe('![alt](/other-vault/attachments/file.png)')
  })

  it('leaves relative and external paths unchanged', () => {
    const relativeImage = '![alt](attachments/file.png)'
    const httpImage = '![logo](https://example.com/logo.png)'

    expect(portableImageUrls(relativeImage, '/vault')).toBe(relativeImage)
    expect(portableImageUrls(httpImage, '/vault')).toBe(httpImage)
  })

  it('handles multiple images', () => {
    const markdown = `![a](${assetUrl('/vault/attachments/a.png')})\n\n![b](${assetUrl('/vault/attachments/b.png')})`

    const result = portableImageUrls(markdown, '/vault')

    expect(result).toContain('![a](attachments/a.png)')
    expect(result).toContain('![b](attachments/b.png)')
  })

  it('preserves title attributes when converting to portable paths', () => {
    const markdown = `![shot](${assetUrl('/vault/attachments/a.png')} "starter vault")`

    expect(portableImageUrls(markdown, '/vault')).toBe('![shot](attachments/a.png "starter vault")')
  })

  it('serializes vault-local asset URLs relative to the active note directory', () => {
    const markdown = `![shot](${assetUrl('/vault/projects/notes/img/Meeting Snapshot.png')})`

    expect(portableImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      '![shot](./img/Meeting Snapshot.png)',
    )
  })

  it('serializes parenthesized vault-local asset URLs with title attributes', () => {
    const markdown = `![shot](${assetUrl('/vault/projects/notes/img/foo(1).png')} "first")`

    expect(portableImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      '![shot](./img/foo(1).png "first")',
    )
  })

  it('serializes vault-local asset URLs with parent traversal when needed', () => {
    const markdown = `![diagram](${assetUrl('/vault/projects/shared/Architecture.png')})`

    expect(portableImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      '![diagram](../shared/Architecture.png)',
    )
  })

  it('serializes root attachment asset URLs relative to nested notes', () => {
    const markdown = `![shot](${assetUrl('/vault/attachments/shot.png')})`

    expect(portableImageUrls(markdown, '/vault', '/vault/projects/notes/plan.md')).toBe(
      '![shot](../../attachments/shot.png)',
    )
  })

  it('serializes Windows root attachment asset URLs relative to nested notes', () => {
    const markdown = `![shot](${assetUrl('C:\\Vault\\attachments\\shot.png')})`

    expect(portableImageUrls(markdown, 'c:\\vault', 'C:\\Vault\\Projects\\Notes\\plan.md')).toBe(
      '![shot](../../attachments/shot.png)',
    )
  })

  it('unwraps external asset URLs to filesystem paths instead of saving asset scheme URLs', () => {
    const markdown = `![external](${assetUrl('/Users/luca/Pictures/photo.png')})`

    expect(portableImageUrls(markdown, '/vault')).toBe('![external](/Users/luca/Pictures/photo.png)')
  })
})

describe('resolveImageUrls / portableImageUrls round-trip', () => {
  it('keeps relative attachment markdown stable', () => {
    tauriMode = true
    const markdown = '![shot](attachments/file.png)'

    expect(portableImageUrls(resolveImageUrls(markdown, '/vault'), '/vault')).toBe(markdown)
  })

  it('keeps note-relative markdown stable', () => {
    tauriMode = true
    const markdown = '![shot](./img/Meeting Snapshot.png)'
    const notePath = '/vault/projects/notes/plan.md'

    expect(
      portableImageUrls(resolveImageUrls(markdown, '/vault', notePath), '/vault', notePath),
    ).toBe(markdown)
  })
})
