import {
  CircleAlert,
  CloudOff,
  GitCompareArrows,
  KeyRound,
  PowerOff,
  UserRoundPlus,
} from 'lucide-react'
import type { LucideIcon } from 'lucide-react'

import { CopyButton } from '@/components/CopyButton'
import { GoogleConsoleLink } from '@/components/GoogleConsoleLink'
import type { GoogleScreen } from '@/components/GoogleConsoleLink'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { t } from '@/lib/i18n'
import type { TranslationKey } from '@/lib/i18n'

/**
 * El tono no mide intensidad: dice **a quién señala** el motivo y qué le pide.
 *
 * `critical` es la credencial rota: algo dejó de funcionar y hay que
 * reemplazarlo. `attention` es la clave que anda y a la que le falta un paso que
 * hace la persona —`NO_PROPERTIES` es el estado **esperado** justo después de
 * subir la clave—. `default` es lo que no depende de nadie de este lado:
 * `PROVIDER_UNAVAILABLE` conserva el estado que la credencial tenía.
 *
 * Pintar de rojo cualquiera de los dos últimos manda a cambiar una clave que
 * anda bien (SC-002), y ésa es la razón por la que este mapa existe.
 */
type Tone = 'critical' | 'attention' | 'default'

interface ErrorReason {
  /**
   * El código, que es con lo que se buscan los textos en el catálogo.
   *
   * Los textos no están acá: este mapa se arma al importar el módulo, antes de
   * que exista el catálogo. Lo que sí vive acá es lo que no depende del idioma
   * —el icono, el tono y qué acciones ofrece cada motivo—.
   */
  code: string
  /** Si la explicación cambia cuando se conoce el identificador del proyecto. */
  namesProject?: boolean
  icon: LucideIcon
  tone: Tone
  /** Pantalla de Google donde se resuelve, de la tabla única de `GoogleConsoleLink`. */
  screen?: GoogleScreen
  /** Si el motivo ofrece un enlace a Google, y a cuál pantalla. */
  hasLink?: boolean
  copyAddress?: boolean
  fixProjectId?: boolean
  hasUploadKey?: boolean
  recheck?: boolean
  /** Si el motivo trae letra chica: lo que hay que saber después de actuar, no antes. */
  hasNote?: boolean
}

/**
 * Los cinco motivos por los que puede fallar la comprobación (FR-054, SC-002).
 *
 * Cada uno se resuelve en una pantalla distinta de Google, así que cada uno
 * tiene su título, su explicación y su acción. Un único «no se pudo comprobar
 * la credencial» deja a la persona probando las cinco cosas a ciegas, que es
 * exactamente la fricción que este producto viene a sacar.
 *
 * El mapa es cerrado a propósito: ante un código que no está, el panel muestra
 * lo que dijo el servidor y **no** da la credencial por inválida.
 */
const REASONS: Record<string, ErrorReason> = {
  INVALID_KEY: {
    code: 'INVALID_KEY',
    icon: KeyRound,
    tone: 'critical',
    screen: 'service-accounts',
    hasLink: true,
    hasUploadKey: true,
    hasNote: true,
  },
  API_NOT_ENABLED: {
    code: 'API_NOT_ENABLED',
    namesProject: true,
    icon: PowerOff,
    // La clave funciona; lo que falta es un interruptor que aprieta la persona
    // en su proyecto. No es una credencial rota.
    tone: 'attention',
    screen: 'enable-api',
    hasLink: true,
    recheck: true,
    hasNote: true,
  },
  PROJECT_MISMATCH: {
    code: 'PROJECT_MISMATCH',
    namesProject: true,
    icon: GitCompareArrows,
    tone: 'critical',
    fixProjectId: true,
    hasUploadKey: true,
  },
  NO_PROPERTIES: {
    code: 'NO_PROPERTIES',
    icon: UserRoundPlus,
    tone: 'attention',
    screen: 'search-console',
    hasLink: true,
    copyAddress: true,
    recheck: true,
    hasNote: true,
  },
  PROVIDER_UNAVAILABLE: {
    code: 'PROVIDER_UNAVAILABLE',
    icon: CloudOff,
    // No hay nada que hacer más que esperar: teñirlo pediría una acción que no
    // existe.
    tone: 'default',
    recheck: true,
    hasNote: true,
  },
}

/** Lo que se muestra ante un código que no está en el mapa. No acusa a la credencial. */
const UNMAPPED: ErrorReason = {
  code: 'UNMAPPED',
  icon: CircleAlert,
  tone: 'default',
  recheck: true,
}

/**
 * El texto de un motivo, buscado por su código.
 *
 * La explicación tiene dos variantes cuando el proyecto se puede nombrar: decir
 * «en el proyecto mi-proyecto-123 la API está apagada» ubica dónde ir, y sin ese
 * dato la frase tiene que poder decirse igual sin él.
 */
function reasonText(reason: ErrorReason, part: string, projectId: string): string {
  const suffix = reason.namesProject && part === 'explanation' && projectId ? '.named' : ''
  return t(`credentialError.${reason.code}.${part}${suffix}` as TranslationKey, { projectId })
}

interface Props {
  /** Código de la última comprobación fallida. */
  errorCode: string
  /** Lo que dijo el servidor. Trae el dato concreto que la explicación no puede tener. */
  message?: string | null
  /** Identificador del proyecto declarado, para nombrarlo en la explicación. */
  projectId?: string | null
  /** Dirección de la cuenta de servicio, para copiarla en `NO_PROPERTIES`. */
  clientEmail?: string | null
  /** Dispara la comprobación otra vez. Sin esto el botón no se ofrece. */
  onRecheck?: () => void
  checking?: boolean
  /** Lleva al paso 1 con el campo del identificador enfocado. */
  onFixProjectId?: () => void
  /** Lleva a la zona de carga del archivo de clave. */
  onUploadKey?: () => void
  /**
   * Cómo se anuncia el aviso.
   *
   * `alert` sólo cuando viene de una comprobación recién hecha; si no, el panel
   * está en pantalla desde que se abrió el paso —diciendo qué falta, que es su
   * trabajo— y dejarlo como región viva permanente haría que abrir la pantalla
   * anunciara como novedad algo que nadie intentó todavía.
   */
  announce?: 'alert' | 'status' | 'none'
  className?: string
}

/**
 * El motivo por el que la credencial no quedó verificada, con qué hacer al
 * respecto.
 *
 * Las acciones se ofrecen sólo cuando la pantalla que lo usa sabe resolverlas:
 * un botón «Corregir el identificador» que no lleva a ningún campo es cableado
 * a medias, peor que no tenerlo.
 */
export function CredentialErrorPanel({
  errorCode,
  message,
  projectId,
  clientEmail,
  onRecheck,
  checking = false,
  onFixProjectId,
  onUploadKey,
  announce = 'alert',
  className,
}: Props) {
  const reason = REASONS[errorCode] ?? UNMAPPED
  const Icon = reason.icon
  const currentProjectId = projectId ?? ''

  // El código sin mapear no tiene explicación propia: lo único honesto que se
  // puede decir es lo que dijo el servidor.
  const explanation = reason === UNMAPPED ? '' : reasonText(reason, 'explanation', currentProjectId)
  const fromServer = message?.trim() ?? ''
  const body = explanation || fromServer || t('serverError.noDetail')
  const detail = explanation && fromServer && fromServer !== explanation ? fromServer : null

  return (
    <Alert
      variant={reason.tone}
      role={announce === 'none' ? undefined : announce}
      className={className}
    >
      <Icon />
      <AlertTitle>{reasonText(reason, 'title', currentProjectId)}</AlertTitle>
      <AlertDescription className="flex flex-col items-start gap-3">
        <span>{body}</span>

        {detail ? <span className="text-xs">{detail}</span> : null}

        {reason.hasNote ? (
          <span className="text-xs">{reasonText(reason, 'note', currentProjectId)}</span>
        ) : null}

        <div className="flex flex-wrap items-center gap-2">
          {reason.copyAddress && clientEmail ? (
            <CopyButton value={clientEmail} label={t('serviceAccount.copy')} />
          ) : null}

          {reason.screen && reason.hasLink ? (
            <GoogleConsoleLink screen={reason.screen} projectId={currentProjectId}>
              {reasonText(reason, 'link', currentProjectId)}
            </GoogleConsoleLink>
          ) : null}

          {reason.fixProjectId && onFixProjectId ? (
            <Button size="sm" variant="outline" onClick={onFixProjectId}>
              {t('credentialError.fixProjectId')}
            </Button>
          ) : null}

          {reason.hasUploadKey && onUploadKey ? (
            <Button size="sm" variant="outline" onClick={onUploadKey}>
              {reasonText(reason, 'uploadKey', currentProjectId)}
            </Button>
          ) : null}

          {reason.recheck && onRecheck ? (
            <Button
              size="sm"
              variant="outline"
              onClick={onRecheck}
              disabled={checking}
              aria-busy={checking || undefined}
            >
              {checking ? t('credentialError.checking') : t('credentialError.recheck')}
            </Button>
          ) : null}
        </div>
      </AlertDescription>
    </Alert>
  )
}

/** El título del motivo, para encabezados que lo necesiten sin rendir el panel entero. */
export function credentialErrorTitle(errorCode: string): string {
  return reasonText(REASONS[errorCode] ?? UNMAPPED, 'title', '')
}
