import { getById, type Actor, type ContentEntry } from '@platform-modules/content';
import type { Querier } from '@platform-modules/db';
import {
  getDefaultLocale,
  listLanguages,
  markStale,
  type I18nContentSchema,
} from '@platform-modules/i18n-content';
import {
  getEntityTranslationStatusFor,
  hashSource,
  translateEntity,
  type EntityTranslationResult,
  type EntityTranslationStatus,
  type SourceTextProvider,
} from '@platform-modules/i18n-translator';
import { setAdapterFactories, type AIMessage, type AIRequest } from '@platform-modules/ai';
import { makeMockAdapter } from '@platform-modules/ai/mock';
import { makeOpenAiCompatAdapter } from '@platform-modules/ai/openai-compat';
import { makeAnthropicAdapter } from '@platform-modules/ai/anthropic';
import { makeGoogleAdapter } from '@platform-modules/ai/google';

export const CONTENT_TRANSLATABLE_FIELDS = ['title', 'body'] as const;

export type TranslationEnv = {
  OPENAI_API_KEY?: string;
  OPENAI_BASE_URL?: string;
  ANTHROPIC_API_KEY?: string;
  ANTHROPIC_BASE_URL?: string;
  GOOGLE_API_KEY?: string;
  MOD_CMS_TRANSLATION_MOCK?: string;
};

export type ContentTranslationStatusByLocale = Record<string, EntityTranslationStatus>;

type ContentTranslationRow = Pick<ContentEntry, 'id' | 'title' | 'body'>;

function createTranslationMockAdapter() {
  const inner = makeMockAdapter();
  return {
    ...inner,
    async call(req: AIRequest) {
      const text = req.messages
        .map((message: AIMessage) =>
          typeof message.content === 'string'
            ? message.content
            : message.content
                .filter(
                  (
                    block,
                  ): block is Extract<(typeof message.content)[number], { type: 'text' }> =>
                    block.type === 'text',
                )
                .map((block: Extract<(typeof message.content)[number], { type: 'text' }>) => block.text)
                .join(''),
        )
        .join('\n');
      const targetLocale = text.match(/to ([a-z-]+)/i)?.[1] ?? 'translated';
      const result: Record<string, string> = {};
      for (const match of text.matchAll(/^- (\w+): (.*)$/gm)) {
        result[match[1]!] = `[${targetLocale}] ${match[2]}`;
      }
      return {
        content: JSON.stringify(result),
        model: req.model,
        provider: 'mock',
        usage: { promptTokens: 1, completionTokens: 1 },
      };
    },
  };
}

function mockTranslationsEnabled(env: TranslationEnv): boolean {
  return (
    env.MOD_CMS_TRANSLATION_MOCK === '1' ||
    env.MOD_CMS_TRANSLATION_MOCK === 'true' ||
    globalThis.process?.env?.MOD_CMS_TRANSLATION_MOCK === '1'
  );
}

export function ensureTranslationAdapters(env: TranslationEnv): void {
  const factories: Parameters<typeof setAdapterFactories>[0] = {};

  if (mockTranslationsEnabled(env)) {
    factories['openai-compat'] = () => createTranslationMockAdapter();
  }
  if (env.OPENAI_API_KEY) {
    factories['openai-compat'] = () =>
      makeOpenAiCompatAdapter({
        apiKey: env.OPENAI_API_KEY!,
        ...(env.OPENAI_BASE_URL ? { baseUrl: env.OPENAI_BASE_URL } : {}),
      });
  }
  if (env.ANTHROPIC_API_KEY) {
    factories.anthropic = () =>
      makeAnthropicAdapter({
        apiKey: env.ANTHROPIC_API_KEY!,
        ...(env.ANTHROPIC_BASE_URL ? { baseUrl: env.ANTHROPIC_BASE_URL } : {}),
      });
  }
  if (env.GOOGLE_API_KEY) {
    factories.google = () => makeGoogleAdapter({ apiKey: env.GOOGLE_API_KEY! });
  }

  setAdapterFactories(factories);
}

function emptyStatusMap(entityIds: readonly string[]): Map<string, ContentTranslationStatusByLocale> {
  return new Map(entityIds.map((entityId) => [entityId, {}]));
}

export async function resolveContentTargetLocales(
  db: Querier<I18nContentSchema>,
): Promise<string[]> {
  return (await listLanguages(db as never, { activeOnly: true }))
    .filter((language) => language.isActive && !language.isDefault)
    .map((language) => language.code);
}

export function createContentSourceProvider(
  db: Querier<I18nContentSchema>,
  actor: Actor,
): SourceTextProvider {
  return {
    async getSourceText({ entityId, fields }) {
      const locale = await getDefaultLocale(db as never);
      const entry = await getById(db as never, entityId, actor);
      if (entry == null) {
        throw new Error(`content entry not found: id=${entityId}`);
      }
      const sourceFields: Record<string, string> = {};
      for (const field of fields) {
        if (field === 'title' || field === 'body') {
          sourceFields[field] = entry[field];
        }
      }
      return { locale, fields: sourceFields };
    },
  };
}

export async function translateContentEntity(
  db: Querier<I18nContentSchema>,
  actor: Actor,
  entityId: string,
): Promise<EntityTranslationResult> {
  const targetLocales = await resolveContentTargetLocales(db);
  return translateEntity(db as never, {
    entityType: 'content',
    entityId,
    fields: CONTENT_TRANSLATABLE_FIELDS,
    targetLocales,
    source: createContentSourceProvider(db, actor),
  });
}

export async function markContentTranslationsStale(
  db: Querier<I18nContentSchema>,
  previous: ContentTranslationRow | null,
  current: ContentTranslationRow,
): Promise<void> {
  if (previous == null) {
    return;
  }

  const fieldHashes: Record<string, string> = {};
  for (const field of CONTENT_TRANSLATABLE_FIELDS) {
    if (previous[field] !== current[field]) {
      fieldHashes[field] = await hashSource(current[field]);
    }
  }

  if (Object.keys(fieldHashes).length === 0) {
    return;
  }

  await markStale(db as never, {
    entityType: 'content',
    entityId: current.id,
    fieldHashes,
  });
}

export async function getContentTranslationStatusesFor(
  db: Querier<I18nContentSchema>,
  entityIds: readonly string[],
): Promise<Map<string, ContentTranslationStatusByLocale>> {
  if (entityIds.length === 0) {
    return new Map();
  }

  const languages = await listLanguages(db as never, { activeOnly: true });
  const nonDefaultLocales = languages.filter((language) => !language.isDefault).map((language) => language.code);
  if (nonDefaultLocales.length === 0) {
    return emptyStatusMap(entityIds);
  }

  const result = emptyStatusMap(entityIds);
  for (const locale of nonDefaultLocales) {
    const statuses = await getEntityTranslationStatusFor(db as never, {
      entityType: 'content',
      entityIds,
      locale,
      fields: CONTENT_TRANSLATABLE_FIELDS,
    });
    for (const entityId of entityIds) {
      result.get(entityId)![locale] = statuses.get(entityId) ?? 'missing';
    }
  }

  return result;
}
