import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { ContentEntry } from '@platform-modules/content';
import type { EntityTranslationStatus } from '@platform-modules/i18n-translator';
import { setAdapterFactories } from '@platform-modules/ai';
import {
  CONTENT_TRANSLATABLE_FIELDS,
  createContentSourceProvider,
  ensureTranslationAdapters,
  getContentTranslationStatusesFor,
  markContentTranslationsStale,
  resolveContentTargetLocales,
  translateContentEntity,
} from './content-translation.js';
import { getById } from '@platform-modules/content';
import { getDefaultLocale, listLanguages, markStale } from '@platform-modules/i18n-content';
import {
  getEntityTranslationStatusFor,
  hashSource,
  translateEntity,
} from '@platform-modules/i18n-translator';

vi.mock('@platform-modules/content', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@platform-modules/content')>();
  return {
    ...actual,
    getById: vi.fn(),
  };
});

vi.mock('@platform-modules/i18n-content', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@platform-modules/i18n-content')>();
  return {
    ...actual,
    getDefaultLocale: vi.fn(),
    listLanguages: vi.fn(),
    markStale: vi.fn(),
  };
});

vi.mock('@platform-modules/ai', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@platform-modules/ai')>();
  return {
    ...actual,
    setAdapterFactories: vi.fn(),
  };
});

vi.mock('@platform-modules/i18n-translator', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@platform-modules/i18n-translator')>();
  return {
    ...actual,
    getEntityTranslationStatusFor: vi.fn(),
    hashSource: vi.fn(),
    translateEntity: vi.fn(),
  };
});

const mockedSetAdapterFactories = vi.mocked(setAdapterFactories);
const mockedGetById = vi.mocked(getById);
const mockedGetDefaultLocale = vi.mocked(getDefaultLocale);
const mockedListLanguages = vi.mocked(listLanguages);
const mockedMarkStale = vi.mocked(markStale);
const mockedGetEntityTranslationStatusFor = vi.mocked(getEntityTranslationStatusFor);
const mockedHashSource = vi.mocked(hashSource);
const mockedTranslateEntity = vi.mocked(translateEntity);

const entry: ContentEntry = {
  id: '11111111-1111-4111-8111-111111111111',
  slug: 'hello-world',
  type: 'post',
  title: 'Hello',
  body: '<p>World</p>',
  status: 'draft',
  visibility: 'public',
  publishedAt: null,
  author: 'author-1',
  terms: [],
  createdAt: new Date('2026-06-30T00:00:00.000Z'),
  updatedAt: new Date('2026-06-30T00:00:00.000Z'),
};

describe('content translation helpers', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('resolveContentTargetLocales returns active non-default locales only', async () => {
    mockedListLanguages.mockResolvedValue([
      { code: 'en', isActive: true, isDefault: true },
      { code: 'fr', isActive: true, isDefault: false },
      { code: 'de', isActive: false, isDefault: false },
      { code: 'es', isActive: true, isDefault: false },
    ] as never);

    await expect(resolveContentTargetLocales({} as never)).resolves.toEqual(['fr', 'es']);
    expect(mockedListLanguages).toHaveBeenCalledWith(expect.anything(), { activeOnly: true });
  });

  it('ensureTranslationAdapters registers the mock openai-compat factory when MOD_CMS_TRANSLATION_MOCK is set', () => {
    ensureTranslationAdapters({ MOD_CMS_TRANSLATION_MOCK: '1' });

    expect(mockedSetAdapterFactories).toHaveBeenCalledWith(
      expect.objectContaining({
        'openai-compat': expect.any(Function),
      }),
    );
  });

  it('createContentSourceProvider reads the stored base columns at the default locale', async () => {
    mockedGetDefaultLocale.mockResolvedValue('en');
    mockedGetById.mockResolvedValue(entry);
    const provider = createContentSourceProvider({} as never, { id: 'admin-1', canEditAny: true });

    const result = await provider.getSourceText({
      entityType: 'content',
      entityId: entry.id,
      fields: ['title', 'body', 'slug'],
    });

    expect(mockedGetDefaultLocale).toHaveBeenCalled();
    expect(mockedGetById).toHaveBeenCalledWith(expect.anything(), entry.id, {
      id: 'admin-1',
      canEditAny: true,
    });
    expect(result).toEqual({
      locale: 'en',
      fields: {
        title: 'Hello',
        body: '<p>World</p>',
      },
    });
  });

  it('translateContentEntity targets active non-default locales and uses the shared provider seam', async () => {
    mockedListLanguages.mockResolvedValue([
      { code: 'en', isActive: true, isDefault: true },
      { code: 'fr', isActive: true, isDefault: false },
      { code: 'de', isActive: false, isDefault: false },
      { code: 'es', isActive: true, isDefault: false },
    ] as never);
    mockedTranslateEntity.mockResolvedValue({ results: [] });

    await translateContentEntity({} as never, { id: 'admin-1', canEditAny: true }, entry.id);

    expect(mockedTranslateEntity).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({
        entityType: 'content',
        entityId: entry.id,
        fields: CONTENT_TRANSLATABLE_FIELDS,
        targetLocales: ['fr', 'es'],
      }),
    );
    expect(mockedTranslateEntity.mock.calls[0]?.[1].source.getSourceText).toBeTypeOf('function');
  });

  it('markContentTranslationsStale hashes only changed translatable fields before calling markStale', async () => {
    mockedHashSource.mockImplementation(async (text) => `hash:${text}`);

    await markContentTranslationsStale(
      {} as never,
      entry,
      {
        ...entry,
        title: 'Hello again',
        body: entry.body,
      },
    );

    expect(mockedHashSource).toHaveBeenCalledTimes(1);
    expect(mockedHashSource).toHaveBeenCalledWith('Hello again');
    expect(mockedMarkStale).toHaveBeenCalledWith(expect.anything(), {
      entityType: 'content',
      entityId: entry.id,
      fieldHashes: { title: 'hash:Hello again' },
    });
  });

  it('markContentTranslationsStale is a no-op when no translatable source field changed', async () => {
    await markContentTranslationsStale({} as never, entry, { ...entry });

    expect(mockedHashSource).not.toHaveBeenCalled();
    expect(mockedMarkStale).not.toHaveBeenCalled();
  });

  it('getContentTranslationStatusesFor projects each non-default active locale into a per-entity map', async () => {
    mockedListLanguages.mockResolvedValue([
      { code: 'en', isActive: true, isDefault: true },
      { code: 'fr', isActive: true, isDefault: false },
      { code: 'es', isActive: true, isDefault: false },
    ] as never);
    mockedGetEntityTranslationStatusFor
      .mockResolvedValueOnce(
        new Map<string, EntityTranslationStatus>([
          ['a', 'complete'],
          ['b', 'stale'],
        ]),
      )
      .mockResolvedValueOnce(
        new Map<string, EntityTranslationStatus>([
          ['a', 'missing'],
          ['b', 'partial'],
        ]),
      );

    const result = await getContentTranslationStatusesFor({} as never, ['a', 'b']);

    expect(mockedGetEntityTranslationStatusFor).toHaveBeenNthCalledWith(1, expect.anything(), {
      entityType: 'content',
      entityIds: ['a', 'b'],
      locale: 'fr',
      fields: CONTENT_TRANSLATABLE_FIELDS,
    });
    expect(mockedGetEntityTranslationStatusFor).toHaveBeenNthCalledWith(2, expect.anything(), {
      entityType: 'content',
      entityIds: ['a', 'b'],
      locale: 'es',
      fields: CONTENT_TRANSLATABLE_FIELDS,
    });
    expect(result.get('a')).toEqual({ fr: 'complete', es: 'missing' });
    expect(result.get('b')).toEqual({ fr: 'stale', es: 'partial' });
  });
});
