import { KeyAuthorization, SignatureEnvelope } from 'ox/tempo'
import { describe, expect, test } from 'vp/test'
import * as z from 'zod/mini'

import * as Rpc from './rpc.js'

const account = '0x0000000000000000000000000000000000000001'
const accessKey = '0x0000000000000000000000000000000000000002'
const token = '0x20c0000000000000000000000000000000000001'
const recipient = '0x0000000000000000000000000000000000000003'
const contract = '0x0000000000000000000000000000000000000004'

describe('transactionRequest.keyAuthorization', () => {
  test('decodes rpc key authorizations into ox key authorizations', () => {
    const authorization = KeyAuthorization.from(
      {
        address: accessKey,
        chainId: 1n,
        expiry: 123,
        limits: [{ token, limit: 100n, period: 60 }],
        scopes: [
          { address: contract },
          { address: token, selector: 'transfer(address,uint256)', recipients: [recipient] },
        ],
        type: 'p256',
      },
      { signature: SignatureEnvelope.from(`0x${'00'.repeat(65)}`) },
    )
    const rpc = KeyAuthorization.toRpc(authorization)

    expect(
      z.decode(Rpc.transactionRequest, {
        from: account,
        keyAuthorization: { ...rpc, address: rpc.keyId },
      }).keyAuthorization,
    ).toMatchInlineSnapshot(`
      {
        "address": "0x0000000000000000000000000000000000000002",
        "chainId": 1n,
        "expiry": 123,
        "limits": [
          {
            "limit": 100n,
            "period": 60,
            "token": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "scopes": [
          {
            "address": "0x0000000000000000000000000000000000000004",
          },
          {
            "address": "0x20c0000000000000000000000000000000000001",
            "recipients": [
              "0x0000000000000000000000000000000000000003",
            ],
            "selector": "0xa9059cbb",
          },
        ],
        "signature": {
          "signature": {
            "r": 0n,
            "s": 0n,
            "yParity": 0,
          },
          "type": "secp256k1",
        },
        "type": "p256",
      }
    `)
  })

  test('encodes ox key authorizations into rpc key authorizations', () => {
    const authorization = KeyAuthorization.from(
      {
        address: accessKey,
        chainId: 1n,
        expiry: 123,
        limits: [{ token, limit: 100n, period: 60 }],
        scopes: [
          { address: contract },
          { address: token, selector: 'transfer(address,uint256)', recipients: [recipient] },
        ],
        type: 'p256',
      },
      { signature: SignatureEnvelope.from(`0x${'00'.repeat(65)}`) },
    )

    expect(
      z.encode(Rpc.transactionRequest, {
        from: account,
        keyAuthorization: authorization,
      }).keyAuthorization,
    ).toMatchInlineSnapshot(`
      {
        "address": "0x0000000000000000000000000000000000000002",
        "allowedCalls": [
          {
            "target": "0x0000000000000000000000000000000000000004",
          },
          {
            "selectorRules": [
              {
                "recipients": [
                  "0x0000000000000000000000000000000000000003",
                ],
                "selector": "0xa9059cbb",
              },
            ],
            "target": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "chainId": "0x1",
        "expiry": "0x7b",
        "keyId": "0x0000000000000000000000000000000000000002",
        "keyType": "p256",
        "limits": [
          {
            "limit": "0x64",
            "period": "0x3c",
            "token": "0x20c0000000000000000000000000000000000001",
          },
        ],
        "signature": {
          "r": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "s": "0x0000000000000000000000000000000000000000000000000000000000000000",
          "type": "secp256k1",
          "yParity": "0x0",
        },
      }
    `)
  })
})

describe('wallet_connect.capabilities.request: auth', () => {
  test('accepts string shorthand', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: '/api/auth',
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": "/api/auth",
        "method": "login",
      }
    `)
  })

  test('accepts object with `url`', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { url: '/api/auth' },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "url": "/api/auth",
        },
        "method": "login",
      }
    `)
  })

  test('accepts object with `url` and `returnToken`', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { url: '/api/auth', returnToken: true },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "returnToken": true,
          "url": "/api/auth",
        },
        "method": "login",
      }
    `)
  })

  test('accepts explicit endpoints (challenge + verify + logout)', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { challenge: '/c', verify: '/v', logout: '/l' },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "challenge": "/c",
          "logout": "/l",
          "verify": "/v",
        },
        "method": "login",
      }
    `)
  })

  test('accepts explicit endpoints with `returnToken` and no `logout`', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { challenge: '/c', verify: '/v', returnToken: true },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "challenge": "/c",
          "returnToken": true,
          "verify": "/v",
        },
        "method": "login",
      }
    `)
  })

  test('accepts explicit endpoints without `logout`', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { challenge: '/c', verify: '/v' },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "challenge": "/c",
          "verify": "/v",
        },
        "method": "login",
      }
    `)
  })

  test('accepts logout-only object (cross-field validation lives outside zod)', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { logout: '/l' },
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "logout": "/l",
        },
        "method": "login",
      }
    `)
  })

  test('rejects auth as number', () => {
    expect(() =>
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: 42,
      }),
    ).toThrow()
  })

  test('rejects auth.url as number', () => {
    expect(() =>
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { url: 1 },
      }),
    ).toThrow()
  })

  test('rejects auth.returnToken as string', () => {
    expect(() =>
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        auth: { returnToken: 'yes' },
      }),
    ).toThrow()
  })

  test('accepts auth alongside register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        auth: '/api/auth',
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": "/api/auth",
        "method": "register",
      }
    `)
  })
})

describe('wallet_connect.capabilities.result: auth + personalSign', () => {
  test('accepts auth with token + personalSign echo + root signature', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.result, {
        auth: { token: 'sess_abc' },
        personalSign: { message: 'hello' },
        signature: '0xdeadbeef',
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {
          "token": "sess_abc",
        },
        "personalSign": {
          "message": "hello",
        },
        "signature": "0xdeadbeef",
      }
    `)
  })

  test('accepts cookie-mode auth (empty object, no token)', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.result, {
        auth: {},
      }),
    ).toMatchInlineSnapshot(`
      {
        "auth": {},
      }
    `)
  })

  test('accepts result without auth/personalSign', () => {
    expect(z.parse(Rpc.wallet_connect.capabilities.result, {})).toMatchInlineSnapshot(`{}`)
  })
})

describe('wallet_connect.capabilities.request: showDeposit', () => {
  test('accepts true on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        showDeposit: true,
      }),
    ).toEqual({
      method: 'register',
      showDeposit: true,
    })
  })

  test('accepts true on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        showDeposit: true,
      }),
    ).toEqual({
      method: 'login',
      showDeposit: true,
    })
  })

  test('accepts false on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        showDeposit: false,
      }),
    ).toEqual({
      method: 'register',
      showDeposit: false,
    })
  })

  test('accepts false on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        showDeposit: false,
      }),
    ).toEqual({
      method: 'login',
      showDeposit: false,
    })
  })

  test('accepts deposit parameters on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        showDeposit: {
          amount: '50',
          displayName: 'DoorDash',
          on: 'register',
          token: 'USDC',
        },
      }),
    ).toEqual({
      method: 'register',
      showDeposit: {
        amount: '50',
        displayName: 'DoorDash',
        on: 'register',
        token: 'USDC',
      },
    })
  })

  test('accepts deposit parameters on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'login',
        showDeposit: {
          amount: '25',
          on: 'login',
          token: 'USDC',
        },
      }),
    ).toEqual({
      method: 'login',
      showDeposit: {
        amount: '25',
        on: 'login',
        token: 'USDC',
      },
    })
  })

  test('rejects invalid deposit parameters', () => {
    expect(() =>
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        showDeposit: { amount: 50 },
      }),
    ).toThrow()
  })

  test('rejects invalid showDeposit event filters', () => {
    expect(() =>
      z.parse(Rpc.wallet_connect.capabilities.request, {
        method: 'register',
        showDeposit: { on: 'connect' },
      }),
    ).toThrow()
  })

  test('omits nested showDeposit from authorizeAccessKey', () => {
    expect(
      z.parse(Rpc.wallet_connect.capabilities.request, {
        authorizeAccessKey: {
          expiry: 123,
          showDeposit: true,
        },
        method: 'register',
      }),
    ).toMatchInlineSnapshot(`
      {
        "authorizeAccessKey": {
          "expiry": 123,
        },
        "method": "register",
      }
    `)
  })
})

describe('wallet_authorizeAccessKey.parameters: showDeposit', () => {
  test('accepts true', () => {
    expect(
      z.parse(Rpc.wallet_authorizeAccessKey.parameters, {
        expiry: 123,
        showDeposit: true,
      }),
    ).toMatchInlineSnapshot(`
      {
        "expiry": 123,
        "showDeposit": true,
      }
    `)
  })

  test('accepts deposit parameters', () => {
    expect(
      z.parse(Rpc.wallet_authorizeAccessKey.parameters, {
        expiry: 123,
        showDeposit: {
          amount: '50',
          displayName: 'DoorDash',
          token: 'USDC',
        },
      }),
    ).toMatchInlineSnapshot(`
      {
        "expiry": 123,
        "showDeposit": {
          "amount": "50",
          "displayName": "DoorDash",
          "token": "USDC",
        },
      }
    `)
  })

  test('rejects invalid deposit parameters', () => {
    expect(() =>
      z.parse(Rpc.wallet_authorizeAccessKey.parameters, {
        expiry: 123,
        showDeposit: { amount: 50 },
      }),
    ).toThrow()
  })
})

describe('wallet_authorizeAccessKey_strict.parameters: showDeposit', () => {
  test('accepts showDeposit with required access-key policy', () => {
    expect(
      z.parse(Rpc.wallet_authorizeAccessKey_strict.parameters, {
        expiry: 123,
        limits: [{ limit: 1n, token: '0x0000000000000000000000000000000000000001' }],
        scopes: [{ address: '0x0000000000000000000000000000000000000001' }],
        showDeposit: true,
      }),
    ).toMatchInlineSnapshot(`
      {
        "expiry": 123,
        "limits": [
          {
            "limit": 1n,
            "token": "0x0000000000000000000000000000000000000001",
          },
        ],
        "scopes": [
          {
            "address": "0x0000000000000000000000000000000000000001",
          },
        ],
        "showDeposit": true,
      }
    `)
  })
})

describe('wallet_connect_strict.parameters: auth', () => {
  test('accepts string shorthand', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'login',
          auth: '/api/auth',
        },
      }),
    ).toMatchInlineSnapshot(`
      {
        "capabilities": {
          "auth": "/api/auth",
          "method": "login",
        },
      }
    `)
  })

  test('accepts object form with all fields', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'login',
          auth: { url: '/api/auth', returnToken: true },
        },
      }),
    ).toMatchInlineSnapshot(`
      {
        "capabilities": {
          "auth": {
            "returnToken": true,
            "url": "/api/auth",
          },
          "method": "login",
        },
      }
    `)
  })
})

describe('wallet_connect_strict.parameters: showDeposit', () => {
  test('accepts true on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'register',
          showDeposit: true,
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'register',
        showDeposit: true,
      },
    })
  })

  test('accepts true on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'login',
          showDeposit: true,
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'login',
        showDeposit: true,
      },
    })
  })

  test('accepts false on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'register',
          showDeposit: false,
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'register',
        showDeposit: false,
      },
    })
  })

  test('accepts false on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'login',
          showDeposit: false,
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'login',
        showDeposit: false,
      },
    })
  })

  test('accepts deposit parameters on the register branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'register',
          showDeposit: {
            amount: '100',
            displayName: 'DoorDash',
            on: 'register',
            token: 'USDC',
          },
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'register',
        showDeposit: {
          amount: '100',
          displayName: 'DoorDash',
          on: 'register',
          token: 'USDC',
        },
      },
    })
  })

  test('accepts deposit parameters on the login branch', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          method: 'login',
          showDeposit: {
            amount: '25',
            on: 'login',
            token: 'USDC',
          },
        },
      }),
    ).toEqual({
      capabilities: {
        method: 'login',
        showDeposit: {
          amount: '25',
          on: 'login',
          token: 'USDC',
        },
      },
    })
  })

  test('omits nested showDeposit from authorizeAccessKey', () => {
    expect(
      z.parse(Rpc.wallet_connect_strict.parameters, {
        capabilities: {
          authorizeAccessKey: {
            expiry: 123,
            limits: [{ limit: 1n, token: '0x0000000000000000000000000000000000000001' }],
            scopes: [{ address: '0x0000000000000000000000000000000000000001' }],
            showDeposit: true,
          },
          method: 'register',
        },
      }),
    ).toMatchInlineSnapshot(`
      {
        "capabilities": {
          "authorizeAccessKey": {
            "expiry": 123,
            "limits": [
              {
                "limit": 1n,
                "token": "0x0000000000000000000000000000000000000001",
              },
            ],
            "scopes": [
              {
                "address": "0x0000000000000000000000000000000000000001",
              },
            ],
          },
          "method": "register",
        },
      }
    `)
  })
})
