// bot/src/__tests__/cardRepository.test.ts
// Testa CardRepository contra banco :memory: mockado.
//
// better-sqlite3 é substituído por mock JS puro (sem binário nativo).
// O mock está em __mocks__/better-sqlite3.js e é mapeado via jest.config.js.

// O mock de db/index expõe uma instância do Database mockado.
// Precisamos expor a instância para poder limpar tabelas entre testes.
const MockDatabase = require('./__mocks__/better-sqlite3')
const memDb = new MockDatabase(':memory:')

jest.mock('../db/index', () => ({
  db: memDb,
}))

import { CardRepository, AccessDeniedError, CardNotFoundError } from '../db/cardRepository'

const clearTables = () => {
  memDb._tables.cards = []
  memDb._tables.card_stages = []
  memDb._nextId.card_stages = 1
}

describe('CardRepository', () => {
  let repo: CardRepository

  beforeEach(() => {
    clearTables()
    repo = new CardRepository()
  })

  const baseCard = (overrides: Record<string, unknown> = {}) => ({
    card_id: 'CARD-1',
    slack_user_id: 'U001',
    slack_channel_id: 'C001',
    titulo: 'Meu card',
    texto_original: 'Texto',
    status: 'em_progresso' as const,
    ...overrides,
  })

  test('insertCard é idempotente para mesmo card_id', () => {
    repo.insertCard(baseCard())
    expect(() => repo.insertCard(baseCard({ titulo: 'Outro título' }))).not.toThrow()
    const card = repo.getCard('CARD-1')
    expect(card?.titulo).toBe('Meu card')
    expect(card?.provider).toBe('jira')
  })

  test('insertCard persiste provider e URL externa do GitHub', () => {
    repo.insertCard(baseCard({
      card_id: 'GH-42',
      provider: 'github',
      external_url: 'https://github.com/acme/api/issues/42',
    }))

    const card = repo.getCard('GH-42')
    expect(card?.provider).toBe('github')
    expect(card?.external_url).toBe('https://github.com/acme/api/issues/42')
  })

  test('assertOwner lança AccessDeniedError para userId diferente (CA-04)', () => {
    repo.insertCard(baseCard())
    expect(() => repo.assertOwner('CARD-1', 'U999')).toThrow(AccessDeniedError)
  })

  test('assertOwner lança CardNotFoundError se card não existe', () => {
    expect(() => repo.assertOwner('INEXISTENTE', 'U001')).toThrow(CardNotFoundError)
  })

  test('assertOwner retorna card quando userId bate (CA-04)', () => {
    repo.insertCard(baseCard())
    const card = repo.assertOwner('CARD-1', 'U001')
    expect(card.card_id).toBe('CARD-1')
  })

  test('listByUser não retorna cards aprovados (CA-06)', () => {
    repo.insertCard(baseCard({ card_id: 'CARD-A', slack_channel_id: 'C100' }))
    repo.insertCard(baseCard({ card_id: 'CARD-B', slack_channel_id: 'C101' }))
    repo.updateStatus('CARD-A', 'aprovado')
    const list = repo.listByUser('U001')
    const ids = list.map((c) => c.card_id)
    expect(ids).not.toContain('CARD-A')
    expect(ids).toContain('CARD-B')
  })

  test('listByUser não retorna cards com deleted_at preenchido (CA-06)', () => {
    repo.insertCard(baseCard({ card_id: 'CARD-D', slack_channel_id: 'C200' }))
    // Simula soft-delete direto na estrutura do mock
    const card = memDb._tables.cards.find((c: { card_id: string }) => c.card_id === 'CARD-D')
    if (card) card.deleted_at = '2024-01-01T00:00:00Z'
    const list = repo.listByUser('U001')
    const ids = list.map((c) => c.card_id)
    expect(ids).not.toContain('CARD-D')
  })

  test('upsertStage sobrescreve conteúdo existente — UNIQUE(card_id, stage) (CA-07)', () => {
    repo.insertCard(baseCard())
    repo.upsertStage('CARD-1', 'idea', 'conteudo v1')
    repo.upsertStage('CARD-1', 'idea', 'conteudo v2')
    const stages = repo.getStages('CARD-1')
    const ideaStages = stages.filter((s) => s.stage === 'idea')
    expect(ideaStages).toHaveLength(1)
    expect(ideaStages[0]!.conteudo).toBe('conteudo v2')
  })

  test('getStages retorna stages na ordem de inserção', () => {
    repo.insertCard(baseCard())
    repo.upsertStage('CARD-1', 'idea', 'ideia')
    repo.upsertStage('CARD-1', 'prd', 'prd')
    repo.upsertStage('CARD-1', 'risk', 'risco')
    const stages = repo.getStages('CARD-1')
    const names = stages.map((s) => s.stage)
    expect(names).toEqual(['idea', 'prd', 'risk'])
  })
})
