import {
  jest,
  describe,
  it,
  expect,
  beforeAll,
  afterEach,
  afterAll
} from '@jest/globals'

const BASE_REPO_ID = 100
const FORK_REPO_ID = 200
const PR_HEAD_SHA = '1111111111111111111111111111111111111111'
const PR_MERGE_SHA = '2222222222222222222222222222222222222222'
const SAFE_BASE_SHA = '3333333333333333333333333333333333333333'
const WORKFLOW_RUN_HEAD_COMMIT_SHA = '4444444444444444444444444444444444444444'
const BASE_QUALIFIED_REPO = 'some-owner/some-repo'
const FORK_QUALIFIED_REPO = 'another-repo/fork'

// Mutable mock context
const mockContext: any = {
  eventName: '',
  payload: {},
  repo: {owner: 'some-owner', repo: 'some-repo'},
  ref: '',
  sha: ''
}

jest.unstable_mockModule('@actions/github', () => ({
  context: mockContext
}))

// Dynamic imports after mocking
const {assertSafePrCheckout} =
  await import('../src/unsafe-pr-checkout-helper.js')

const originalEventName = mockContext.eventName
const originalPayload = mockContext.payload

function setContext(eventName: string, payload: object): void {
  mockContext.eventName = eventName
  mockContext.payload = payload
}

function forkPullRequestTargetPayload(): object {
  return {
    repository: {id: BASE_REPO_ID},
    pull_request: {
      head: {
        sha: PR_HEAD_SHA,
        repo: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
      },
      merge_commit_sha: PR_MERGE_SHA
    }
  }
}

function sameRepoPullRequestTargetPayload(): object {
  return {
    repository: {id: BASE_REPO_ID},
    pull_request: {
      head: {
        sha: PR_HEAD_SHA,
        repo: {id: BASE_REPO_ID, full_name: BASE_QUALIFIED_REPO}
      },
      merge_commit_sha: PR_MERGE_SHA
    }
  }
}

function forkWorkflowRunPayload(): object {
  return {
    repository: {id: BASE_REPO_ID},
    workflow_run: {
      event: 'pull_request',
      head_commit: {id: WORKFLOW_RUN_HEAD_COMMIT_SHA},
      head_repository: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
    }
  }
}

describe('unsafe-pr-checkout-helper', () => {
  beforeAll(() => {
    mockContext.repo = {owner: 'some-owner', repo: 'some-repo'}
  })

  afterEach(() => {
    mockContext.eventName = originalEventName
    mockContext.payload = originalPayload
  })

  afterAll(() => {
    mockContext.eventName = originalEventName
    mockContext.payload = originalPayload
  })

  it('allows pull_request events untouched', () => {
    setContext('pull_request', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: 'attacker/fork',
        ref: 'refs/pull/1/merge',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).not.toThrow()
  })

  it('allows pull_request_target default checkout (base branch)', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: 'refs/heads/main',
        commit: SAFE_BASE_SHA,
        allowUnsafePrCheckout: false
      })
    ).not.toThrow()
  })

  it('allows same-repo pull_request_target checkout of PR head', () => {
    setContext('pull_request_target', sameRepoPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: PR_HEAD_SHA,
        allowUnsafePrCheckout: false
      })
    ).not.toThrow()
  })

  it('refuses pull_request_target fork PR head SHA checkout', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: PR_HEAD_SHA,
        allowUnsafePrCheckout: false
      })
    ).toThrow(/Refusing to check out fork pull request code/)
  })

  it('refuses pull_request_target fork PR merge_commit_sha checkout', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: PR_MERGE_SHA,
        allowUnsafePrCheckout: false
      })
    ).toThrow(/allow-unsafe-pr-checkout/)
  })

  it('refuses pull_request_target fork PR ref pattern (head)', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: 'refs/pull/42/head',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('refuses pull_request_target fork PR ref pattern (merge)', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: 'refs/pull/42/merge',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('refuses pull_request_target when repository points at the fork', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: FORK_QUALIFIED_REPO,
        ref: 'refs/heads/main',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('allows pull_request_target checkout of an unrelated third-party repo', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: 'some-other/unrelated',
        ref: 'refs/heads/main',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).not.toThrow()
  })

  it('refuses pull_request_target ignoring repository case differences', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: FORK_QUALIFIED_REPO.toUpperCase(),
        ref: '',
        commit: '',
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('refuses pull_request_target ignoring commit SHA case differences', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: PR_HEAD_SHA.toUpperCase(),
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('allows pull_request_target fork PR checkout when opted in', () => {
    setContext('pull_request_target', forkPullRequestTargetPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: 'refs/pull/42/merge',
        commit: '',
        allowUnsafePrCheckout: true
      })
    ).not.toThrow()
  })

  it('refuses workflow_run fork PR head_commit.id checkout', () => {
    setContext('workflow_run', forkWorkflowRunPayload())
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('refuses workflow_run with pull_request_target underlying event', () => {
    const payload = forkWorkflowRunPayload() as {
      workflow_run: {event: string}
    }
    payload.workflow_run.event = 'pull_request_target'
    setContext('workflow_run', payload)
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
        allowUnsafePrCheckout: false
      })
    ).toThrow()
  })

  it('allows workflow_run same-repo PR (head_repository.id matches base)', () => {
    const payload = forkWorkflowRunPayload() as {
      workflow_run: {head_repository: {id: number}}
    }
    payload.workflow_run.head_repository.id = BASE_REPO_ID
    setContext('workflow_run', payload)
    expect(() =>
      assertSafePrCheckout({
        qualifiedRepository: BASE_QUALIFIED_REPO,
        ref: '',
        commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
        allowUnsafePrCheckout: false
      })
    ).not.toThrow()
  })
})
