import { Address, Authorization, Secp256k1 } from 'ox'
import { SignatureErc8010 } from 'ox/erc8010'
import { describe, expect, test } from 'vp/test'
import { accounts } from '../../../test/constants/accounts.js'

const authorization_unsigned = Authorization.from({
  address: '0x0000000000000000000000000000000000000000',
  chainId: 1,
  nonce: 69n,
})
const authorization_signature = Secp256k1.sign({
  payload: Authorization.getSignPayload(authorization_unsigned),
  privateKey: accounts[0].privateKey,
})
const authorization = Authorization.from(authorization_unsigned, {
  signature: authorization_signature,
})

const signature =
  '0xfa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c'

describe('assert', () => {
  test('default', () => {
    const wrapped = SignatureErc8010.wrap({
      authorization,
      data: '0xcafebabe',
      signature,
    })

    SignatureErc8010.assert(wrapped)
    expect(() =>
      SignatureErc8010.assert('0xdeadbeef'),
    ).toThrowErrorMatchingInlineSnapshot(
      '[SignatureErc8010.InvalidWrappedSignatureError: Value `0xdeadbeef` is an invalid ERC-8010 wrapped signature.]',
    )
  })
})

describe('wrap', () => {
  test('default', () => {
    expect(
      SignatureErc8010.wrap({
        authorization,
        data: '0xdeadbeef',
        signature,
      }),
    ).toMatchInlineSnapshot(
      `"0xfa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001e345a5dc9a8f8d6cdea46e6991bee5d93b32c5cb9313faeff4ed6aa2e1b3c6b14486b72b28fed042eff14e56d05ecbf1f6f4a622db124104b26ee6f1d0e7715a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000004deadbeef0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001408010801080108010801080108010801080108010801080108010801080108010"`,
    )
  })

  test('behavior: no data', () => {
    expect(
      SignatureErc8010.wrap({
        authorization,
        signature,
      }),
    ).toMatchInlineSnapshot(
      `"0xfa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001e345a5dc9a8f8d6cdea46e6991bee5d93b32c5cb9313faeff4ed6aa2e1b3c6b14486b72b28fed042eff14e56d05ecbf1f6f4a622db124104b26ee6f1d0e7715a000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001208010801080108010801080108010801080108010801080108010801080108010"`,
    )
  })

  test('behavior: invalid auth', () => {
    expect(() =>
      SignatureErc8010.wrap({
        authorization: {
          address: '0x1234567890abcdef1234567890abcdef12345678',
          chainId: 1,
          nonce: 69n,
          r: '0x0000000000000000000000000000000000000000000000000000000000000000',
          s: '0x0000000000000000000000000000000000000000000000000000000000000000',
          yParity: -1,
        },
        data: '0xdeadbeef',
        signature,
      }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[Signature.InvalidYParityError: Value \`-1\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
    )
  })
})

describe('from', () => {
  test('default', () => {
    const args = {
      authorization,
      data: '0xdeadbeef',
      signature,
    } as const

    const wrapped = SignatureErc8010.from(args)
    expect(wrapped).toEqual(args)
  })

  test('behavior: wrapped', () => {
    const args = {
      authorization,
      data: '0xdeadbeef',
      signature,
    } as const

    const serialized = SignatureErc8010.wrap(args)
    const { to, ...wrapped } = SignatureErc8010.from(serialized)
    expect(to).toBe(Address.checksum(accounts[0].address))
    expect(wrapped).toEqual(args)
  })
})

describe('unwrap', () => {
  test('default', () => {
    const args = {
      authorization,
      data: '0xdeadbeef',
      signature,
    } as const

    const wrapped = SignatureErc8010.wrap(args)
    const { to, ...unwrapped } = SignatureErc8010.unwrap(wrapped)
    expect(to).toBe(Address.checksum(accounts[0].address))
    expect(unwrapped).toEqual(args)
  })

  test('behavior: no data', () => {
    const args = {
      authorization,
      signature,
    } as const

    const wrapped = SignatureErc8010.wrap(args)
    const { data, to, ...unwrapped } = SignatureErc8010.unwrap(wrapped)
    expect(data).toBeUndefined()
    expect(to).toBeUndefined()
    expect(unwrapped).toEqual(args)
  })
})

describe('validate', () => {
  test('default', () => {
    const wrapped = SignatureErc8010.wrap({
      authorization,
      data: '0xdeadbeef',
      signature,
    })

    const valid = SignatureErc8010.validate(wrapped)
    expect(valid).toBe(true)
    expect(SignatureErc8010.validate('0xdeadbeef')).toBe(false)
  })
})

describe('assert: validates unwrapped objects', () => {
  test('throws on malformed object', () => {
    expect(() =>
      // @ts-expect-error
      SignatureErc8010.assert(null),
    ).toThrow(SignatureErc8010.InvalidUnwrappedSignatureError)

    expect(() =>
      // @ts-expect-error
      SignatureErc8010.assert({ authorization, data: '0xdeadbeef' }),
    ).toThrow(SignatureErc8010.InvalidUnwrappedSignatureError)

    expect(() =>
      SignatureErc8010.assert({
        // @ts-expect-error
        authorization: null,
        signature,
      }),
    ).toThrow(SignatureErc8010.InvalidUnwrappedSignatureError)
  })
})

describe('wrap: skips recovery when `to` is provided', () => {
  test('uses caller-supplied `to` verbatim', () => {
    const customTo = '0xCafEBAbECAFEbAbEcaFEbabECAfebAbEcAFEBaBe' as const
    const wrapped = SignatureErc8010.wrap({
      authorization,
      data: '0xdeadbeef',
      signature,
      to: customTo,
    })
    const unwrapped = SignatureErc8010.unwrap(wrapped)
    expect(unwrapped.to).toBe(customTo)
  })

  test('still recovers when `to` is absent', () => {
    const wrapped = SignatureErc8010.wrap({
      authorization,
      data: '0xdeadbeef',
      signature,
    })
    const unwrapped = SignatureErc8010.unwrap(wrapped)
    expect(unwrapped.to).toBe(Address.checksum(accounts[0].address))
  })
})

describe('unwrap: rejects oversized suffix length', () => {
  test('throws InvalidWrappedSignatureError on out-of-range suffix length', () => {
    // Construct a malicious wrapped value: short signature + impossibly large
    // suffix length word + magic bytes. The naive Number(hex) parse used to
    // produce a valid slice; the safe parse must reject.
    const shortSig = `0x${'00'.repeat(8)}`
    const huge = `0x${'ff'.repeat(32)}`
    const malicious = (shortSig +
      huge.slice(2) +
      SignatureErc8010.magicBytes.slice(2)) as `0x${string}`
    expect(() => SignatureErc8010.unwrap(malicious)).toThrow(
      SignatureErc8010.InvalidWrappedSignatureError,
    )
  })
})
