import { and, eq, ne, sql } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { UnknownLocaleError } from './errors.js'
import { isLocaleActive } from './languages.js'
import type { MarkFailedArgs, MarkStaleArgs, SetTranslationArgs } from './model.js'
import { translationValue, type I18nContentSchema } from './schema.js'
import { assertEntityId, assertEntityType, assertFieldKey } from './validate.js'

export async function setTranslation(
  q: Querier<I18nContentSchema>,
  args: SetTranslationArgs,
): Promise<void> {
  assertEntityType(args.entityType)
  assertFieldKey(args.fieldKey)
  assertEntityId(args.entityId)

  // KNOWN: non-transactional check before upsert — locale-deactivation race is an admin-time event; FK on locale prevents truly invalid codes; accepted.
  if (!(await isLocaleActive(q, args.locale))) {
    throw new UnknownLocaleError(args.locale)
  }

  const manualOverride = args.manualOverride ?? false
  const sourceHash = args.sourceHash ?? ''
  const modelId = args.modelId ?? null
  const isProtectedMachineWrite = !manualOverride

  const conflictTarget = [
    translationValue.entityType,
    translationValue.entityId,
    translationValue.fieldKey,
    translationValue.locale,
  ]

  if (isProtectedMachineWrite) {
    await q
      .insert(translationValue)
      .values({
        entityType: args.entityType,
        entityId: args.entityId,
        fieldKey: args.fieldKey,
        locale: args.locale,
        value: args.value,
        sourceHash,
        modelId,
        manualOverride: false,
        status: 'OK',
        translatedAt: sql`now()`,
        updatedAt: sql`now()`,
      })
      .onConflictDoUpdate({
        target: conflictTarget,
        set: {
          value: sql`excluded.value`,
          sourceHash: sql`excluded.source_hash`,
          modelId: sql`excluded.model_id`,
          status: sql`'OK'`,
          translatedAt: sql`now()`,
          updatedAt: sql`now()`,
        },
        setWhere: sql`NOT ${translationValue.manualOverride}`,
      })
    return
  }

  await q
    .insert(translationValue)
    .values({
      entityType: args.entityType,
      entityId: args.entityId,
      fieldKey: args.fieldKey,
      locale: args.locale,
      value: args.value,
      sourceHash,
      modelId,
      manualOverride,
      status: 'OK',
      translatedAt: sql`now()`,
      updatedAt: sql`now()`,
    })
    .onConflictDoUpdate({
      target: conflictTarget,
      set: {
        value: args.value,
        sourceHash,
        modelId,
        manualOverride,
        status: 'OK',
        translatedAt: sql`now()`,
        updatedAt: sql`now()`,
      },
    })
}

export async function markFailed(
  q: Querier<I18nContentSchema>,
  args: MarkFailedArgs,
): Promise<void> {
  assertEntityType(args.entityType)
  assertFieldKey(args.fieldKey)
  assertEntityId(args.entityId)

  // KNOWN: non-transactional check before upsert — locale-deactivation race is an admin-time event; FK on locale prevents truly invalid codes; accepted.
  if (!(await isLocaleActive(q, args.locale))) {
    throw new UnknownLocaleError(args.locale)
  }

  const sourceHash = args.sourceHash ?? ''
  const modelId = args.modelId ?? null

  const conflictTarget = [
    translationValue.entityType,
    translationValue.entityId,
    translationValue.fieldKey,
    translationValue.locale,
  ]

  await q
    .insert(translationValue)
    .values({
      entityType: args.entityType,
      entityId: args.entityId,
      fieldKey: args.fieldKey,
      locale: args.locale,
      value: '',
      sourceHash,
      modelId,
      manualOverride: false,
      status: 'FAILED',
      translatedAt: null,
      updatedAt: sql`now()`,
    })
    .onConflictDoUpdate({
      target: conflictTarget,
      set: {
        value: sql`''`,
        sourceHash: sql`excluded.source_hash`,
        modelId: sql`excluded.model_id`,
        status: sql`'FAILED'`,
        translatedAt: sql`null`,
        updatedAt: sql`now()`,
      },
      // MUST NOT downgrade an existing OK row (spec §8): a transient error must
      // not destroy published content. Only rows that are missing, STALE, or
      // already FAILED may be written — an OK row is preserved as-is.
      setWhere: sql`NOT ${translationValue.manualOverride} AND ${translationValue.status} <> 'OK'`,
    })
}

export async function markStale(
  q: Querier<I18nContentSchema>,
  args: MarkStaleArgs,
): Promise<void> {
  assertEntityType(args.entityType)
  assertEntityId(args.entityId)

  const entries = Object.entries(args.fieldHashes)
  if (entries.length === 0) {
    return
  }

  for (const [field, newHash] of entries) {
    assertFieldKey(field)
    await q
      .update(translationValue)
      .set({ status: 'STALE', updatedAt: sql`now()` })
      .where(
        and(
          eq(translationValue.entityType, args.entityType),
          eq(translationValue.entityId, args.entityId),
          eq(translationValue.fieldKey, field),
          eq(translationValue.manualOverride, false),
          ne(translationValue.status, sql`'FAILED'`),
          ne(translationValue.sourceHash, newHash),
        ),
      )
  }
}