// bot/src/commands/create.ts — handler para /bot create <texto>
//
// Segurança:
//   S-04: captura erros e aplica redactSecrets antes de logar ou enviar ao Slack
//   S-05: valida card_id retornado pelo Jira com regex antes de qualquer uso
//   S-06: execa sem shell:true, env restrito (PATH, HOME, SDD_BOT_CONTEXT)

import fs from 'node:fs'
import path from 'node:path'
import { execa } from 'execa'
import type { WebClient } from '@slack/web-api'
import type { LLMProvider } from '../providers/llm/index'
import type { RepoProvider, WorkTrackerProvider } from '../providers/contracts'
import { cardRepository } from '../db/cardRepository'
import { extractProjectKey, ChannelKeyMissingError } from '../channelKey'
import { redactSecrets } from './bot'
import type { StageName } from '../types'

// ── Constantes ────────────────────────────────────────────────────────────────

const CARD_ID_RE = /^(?:[A-Z][A-Z0-9]+-\d+|GH-\d+)$/
const MAX_TEXT_LEN = 4000
const SLACK_UPLOAD_THRESHOLD = 3000

// Mapa de etapa para arquivo de artefato gerado pelo orquestrador SDD
const STAGE_FILE: Record<StageName, string> = {
  risk: '00-risk.md',
  idea: '01-idea.md',
  prd:  '02-prd.md',
}

// ── runOrchestrator ───────────────────────────────────────────────────────────

export async function runOrchestrator(cardId: string, cwd: string) {
  if (!CARD_ID_RE.test(cardId)) throw new Error(`Card ID inválido: ${cardId}`)
  return execa('sdd', ['orchestrator', cardId], {
    cwd,
    env: {
      PATH: process.env['PATH'],
      HOME: process.env['HOME'],
      SDD_BOT_CONTEXT: 'true',
    },
    reject: false,
    timeout: 300_000,
  })
}

// ── handleCreate ──────────────────────────────────────────────────────────────

export interface CreateDeps {
  llm: LLMProvider
  workTrackerProvider: WorkTrackerProvider
  repoProvider?: RepoProvider
  worktreePath: string   // caminho raiz de worktrees (.worktree/)
}

export async function handleCreate(
  texto: string,
  command: { user_id: string; channel_id: string },
  respond: (payload: { text: string; response_type?: string }) => Promise<void>,
  client: WebClient,
  deps: CreateDeps,
): Promise<void> {
  const { llm, repoProvider, workTrackerProvider, worktreePath } = deps
  const userId    = command.user_id
  const channelId = command.channel_id

  const ephemeral = async (text: string): Promise<void> => {
    await client.chat.postEphemeral({ channel: channelId, user: userId, text })
  }

  // 1. Validar texto
  const textoClean = texto.trim()
  if (!textoClean) {
    await ephemeral('❌ Informe o texto da demanda. Uso: `/bot create <texto>`')
    return
  }
  if (textoClean.length > MAX_TEXT_LEN) {
    await ephemeral(`❌ Texto muito longo (${textoClean.length} chars). Máximo: ${MAX_TEXT_LEN}.`)
    return
  }

  // 2. Extrair projectKey do canal quando o work tracker é Jira.
  let projectKey = ''
  if (workTrackerProvider.kind === 'jira') {
    try {
      projectKey = await extractProjectKey(client, channelId)
    } catch (err) {
      if (err instanceof ChannelKeyMissingError) {
        await ephemeral(
          `❌ Este canal não tem a chave do projeto Jira configurada.\n` +
          `Adicione \`key: PROJ\` no tópico ou descrição do canal e tente novamente.\n` +
          `Use \`/bot help\` para mais detalhes.`,
        )
        return
      }
      throw err
    }
  }

  // 3. Verificar se canal já tem card ativo (evita violação UNIQUE(slack_channel_id))
  const existing = cardRepository.getCardByChannel(channelId)
  if (existing) {
    await respond({
      text: `⚠️ Este canal já tem um card ativo: *${existing.card_id}* — ${existing.titulo}\nUse */bot view ${existing.card_id}* para ver o status ou */bot update ${existing.card_id} <novo texto>* para atualizar.`
    })
    return
  }

  // 4. Feedback imediato
  await ephemeral(`⏳ Criando card no ${workTrackerProvider.label}...`)

  // 5. Gerar título via LLM
  let titulo: string
  try {
    titulo = await llm.generateTitle(textoClean)
  } catch (err) {
    console.error('[create] generateTitle falhou:', redactSecrets(String(err)))
    titulo = textoClean.slice(0, 80)
  }

  // 6. Criar card no work tracker configurado
  let card_id: string
  try {
    card_id = await workTrackerProvider.createCard(titulo, textoClean)
  } catch (err) {
    const msg = redactSecrets(String(err))
    console.error('[create] workTrackerProvider.createCard falhou:', msg)
    await ephemeral(`❌ Falha ao criar card no ${workTrackerProvider.label}: ${msg}`)
    return
  }

  // 7. Validar card_id retornado (S-05)
  if (!CARD_ID_RE.test(card_id) || !workTrackerProvider.isValidCardId(card_id)) {
    const msg = `card_id retornado pelo ${workTrackerProvider.label} é inválido: "${redactSecrets(card_id)}"`
    console.error(`[create] S-05 ${msg}`)
    await ephemeral(`❌ Erro interno: ${msg}`)
    return
  }

  // 8. Inserir no SQLite
  cardRepository.insertCard({
    card_id,
    slack_user_id:    userId,
    slack_channel_id: channelId,
    titulo,
    texto_original:   textoClean,
    status:           'em_progresso',
    provider:         workTrackerProvider.kind,
    external_url:     workTrackerProvider.cardUrl(card_id),
  })

  // Caminho da worktree deste card
  const cardWorktree = path.join(worktreePath, card_id)

  // 8. Loop pelas etapas
  const stages: StageName[] = ['risk', 'idea', 'prd']
  for (const stage of stages) {
    await ephemeral(`⏳ Gerando ${stage}...`)

    try {
      await runOrchestrator(card_id, cardWorktree)
    } catch (err) {
      console.error(`[create] runOrchestrator falhou para ${stage}:`, redactSecrets(String(err)))
      // continua para tentar ler o artefato gerado parcialmente
    }

    // Ler artefato gerado pelo orquestrador
    const artifactFile = path.join(cardWorktree, 'docs', card_id, STAGE_FILE[stage])
    let conteudo = ''
    try {
      conteudo = fs.readFileSync(artifactFile, 'utf-8')
    } catch {
      conteudo = `*(artefato ${stage} não encontrado em ${artifactFile})*`
    }

    // Persistir no SQLite
    cardRepository.upsertStage(card_id, stage, conteudo)

    // Enviar ao Slack
    if (conteudo.length > SLACK_UPLOAD_THRESHOLD) {
      try {
        await client.files.uploadV2({
          channel_id: channelId,
          content:    conteudo,
          filename:   `${card_id}-${stage}.md`,
          title:      `${card_id} — ${stage}`,
        })
      } catch (err) {
        console.error(`[create] uploadV2 falhou para ${stage}:`, redactSecrets(String(err)))
        await ephemeral(`⚠️ Não foi possível fazer upload do artefato ${stage}.`)
      }
    } else {
      await ephemeral(`*${card_id} — ${stage}*\n\n${conteudo}`)
    }
  }

  // 9. Adicionar comentário /bloqueado no work tracker
  try {
    await workTrackerProvider.addComment(card_id, '/bloqueado')
  } catch (err) {
    console.error('[create] addComment falhou:', redactSecrets(String(err)))
    // não bloqueia o fluxo
  }

  const pullRequest = await createDraftPullRequestIfReady({
    cardId: card_id,
    cardWorktree,
    repoProvider,
    titulo,
    textoOriginal: textoClean,
    workTrackerUrl: workTrackerProvider.cardUrl(card_id),
    notify: ephemeral,
  })

  // 10. Mensagem final
  await ephemeral(
    `✅ Card *${card_id}* criado e documentado até o PRD.\n` +
    `Use \`/bot aproved ${card_id}\` para liberar.\n` +
    `${workTrackerProvider.kind === 'jira' ? `Projeto Jira: *${projectKey}*` : `Issue GitHub: ${workTrackerProvider.cardUrl(card_id) ?? card_id}`}` +
    `${pullRequest ? `\nDraft PR: ${pullRequest.url}` : ''}`,
  )
}

interface DraftPullRequestInput {
  cardId: string
  cardWorktree: string
  repoProvider?: RepoProvider
  titulo: string
  textoOriginal: string
  workTrackerUrl: string | null
  notify: (text: string) => Promise<void>
}

async function createDraftPullRequestIfReady(
  input: DraftPullRequestInput,
): Promise<{ number: number; url: string } | null> {
  const { cardId, cardWorktree, repoProvider, titulo, textoOriginal, workTrackerUrl, notify } = input
  if (!repoProvider?.createPullRequest || !fs.existsSync(cardWorktree)) return null

  try {
    await repoProvider.ensureRemoteBranch(cardId)
    const pullRequest = await repoProvider.createPullRequest(
      cardId,
      `${cardId}: ${titulo}`,
      draftPullRequestBody(cardId, textoOriginal, workTrackerUrl),
      true,
    )
    await registerPullRequestLink(cardWorktree, cardId, titulo, pullRequest)
    return pullRequest
  } catch (err) {
    const msg = redactSecrets(String(err))
    console.error('[create] createDraftPullRequest falhou:', msg)
    await notify(`⚠️ Card criado, mas não foi possível criar o draft PR: ${msg}`)
    return null
  }
}

function draftPullRequestBody(
  cardId: string,
  textoOriginal: string,
  workTrackerUrl: string | null,
): string {
  return [
    `SDD card: ${cardId}`,
    workTrackerUrl ? `Work tracker: ${workTrackerUrl}` : null,
    '',
    'Resumo original:',
    textoOriginal,
    '',
    'Criado como draft para revisão humana. O bot não faz merge automático.',
  ].filter((line): line is string => line !== null).join('\n')
}

async function registerPullRequestLink(
  cardWorktree: string,
  cardId: string,
  titulo: string,
  pullRequest: { number: number; url: string },
): Promise<void> {
  const result = await execa('sdd', [
    'artifact',
    'link',
    cardId,
    'github',
    '--url',
    pullRequest.url,
    '--kind',
    'pull_request',
    '--id',
    String(pullRequest.number),
    '--title',
    `${cardId}: ${titulo}`,
    '--stage',
    'execution',
  ], {
    cwd: cardWorktree,
    env: {
      PATH: process.env['PATH'],
      HOME: process.env['HOME'],
      SDD_BOT_CONTEXT: 'true',
    },
    reject: false,
  })

  if (result.exitCode !== 0) {
    console.warn('[create] artifact link PR falhou:', redactSecrets(result.stderr || result.stdout))
  }
}
