import { KikuMergePreviewResponse } from '../types/anki';
import { createLogger } from '../logger';
import { getPreferredWordValueFromExtractedFields } from '../anki-field-config';

const log = createLogger('anki').child('integration.field-grouping');

interface FieldGroupingNoteInfo {
  noteId: number;
  fields: Record<string, { value: string }>;
}

interface FieldGroupingDeps {
  getConfig: () => {
    fields?: {
      word?: string;
    };
  };
  getEffectiveSentenceCardConfig: () => {
    model?: string;
    sentenceField: string;
    audioField: string;
    lapisEnabled: boolean;
    fieldGroupingProvider: 'kiku' | 'senren' | null;
    fieldGroupingMode: 'auto' | 'manual' | 'disabled';
  };
  isUpdateInProgress: () => boolean;
  getDeck?: () => string | undefined;
  withUpdateProgress: <T>(initialMessage: string, action: () => Promise<T>) => Promise<T>;
  showOsdNotification: (text: string) => void;
  findNotes: (
    query: string,
    options?: {
      maxRetries?: number;
    },
  ) => Promise<number[]>;
  notesInfo: (noteIds: number[]) => Promise<FieldGroupingNoteInfo[]>;
  extractFields: (fields: Record<string, { value: string }>) => Record<string, string>;
  findDuplicateNote: (
    expression: string,
    excludeNoteId: number,
    noteInfo: FieldGroupingNoteInfo,
  ) => Promise<number | null>;
  getTrackedDuplicateNoteIds?: (noteId: number) => number[] | null;
  hasAllConfiguredFields: (
    noteInfo: FieldGroupingNoteInfo,
    configuredFieldNames: (string | undefined)[],
  ) => boolean;
  processNewCard: (noteId: number, options?: { skipFieldGrouping?: boolean }) => Promise<void>;
  getSentenceCardImageFieldName: () => string | undefined;
  resolveFieldName: (availableFieldNames: string[], preferredName: string) => string | null;
  computeFieldGroupingMergedFields: (
    keepNoteId: number,
    deleteNoteId: number,
    keepNoteInfo: FieldGroupingNoteInfo,
    deleteNoteInfo: FieldGroupingNoteInfo,
    includeGeneratedMedia: boolean,
  ) => Promise<Record<string, string>>;
  getNoteFieldMap: (noteInfo: FieldGroupingNoteInfo) => Record<string, string>;
  handleFieldGroupingAuto: (
    originalNoteId: number,
    newNoteId: number,
    newNoteInfo: FieldGroupingNoteInfo,
    expression: string,
  ) => Promise<void>;
  handleFieldGroupingManual: (
    originalNoteId: number,
    newNoteId: number,
    newNoteInfo: FieldGroupingNoteInfo,
    expression: string,
  ) => Promise<boolean>;
}

export class FieldGroupingService {
  constructor(private readonly deps: FieldGroupingDeps) {}

  async triggerFieldGroupingForLastAddedCard(): Promise<void> {
    const sentenceCardConfig = this.deps.getEffectiveSentenceCardConfig();
    if (sentenceCardConfig.fieldGroupingProvider === null) {
      this.deps.showOsdNotification('Field grouping requires Kiku or Senren mode');
      return;
    }
    if (sentenceCardConfig.fieldGroupingMode === 'disabled') {
      this.deps.showOsdNotification('Field grouping is disabled');
      return;
    }

    if (this.deps.isUpdateInProgress()) {
      this.deps.showOsdNotification('Anki update already in progress');
      return;
    }

    try {
      await this.deps.withUpdateProgress('Grouping duplicate cards', async () => {
        const deck = this.deps.getDeck ? this.deps.getDeck() : undefined;
        const query = deck ? `"deck:${deck}" added:1` : 'added:1';
        const noteIds = await this.deps.findNotes(query);
        if (!noteIds || noteIds.length === 0) {
          this.deps.showOsdNotification('No recently added cards found');
          return;
        }

        const noteId = Math.max(...noteIds);
        const notesInfoResult = await this.deps.notesInfo([noteId]);
        const notesInfo = notesInfoResult as FieldGroupingNoteInfo[];
        if (!notesInfo || notesInfo.length === 0) {
          this.deps.showOsdNotification('Card not found');
          return;
        }
        const noteInfoBeforeUpdate = notesInfo[0]!;
        const fields = this.deps.extractFields(noteInfoBeforeUpdate.fields);
        const expressionText = getPreferredWordValueFromExtractedFields(
          fields,
          this.deps.getConfig(),
        );
        if (!expressionText) {
          this.deps.showOsdNotification('No expression/word field found');
          return;
        }

        const trackedDuplicateNoteIds = this.deps.getTrackedDuplicateNoteIds?.(noteId) ?? null;
        const duplicateNoteId =
          trackedDuplicateNoteIds !== null
            ? pickMostRecentDuplicateNoteId(trackedDuplicateNoteIds, noteId)
            : await this.deps.findDuplicateNote(expressionText, noteId, noteInfoBeforeUpdate);
        if (duplicateNoteId === null) {
          this.deps.showOsdNotification('No duplicate card found');
          return;
        }

        if (
          !this.deps.hasAllConfiguredFields(noteInfoBeforeUpdate, [
            this.deps.getSentenceCardImageFieldName(),
          ])
        ) {
          await this.deps.processNewCard(noteId, {
            skipFieldGrouping: true,
          });
        }

        const refreshedInfoResult = await this.deps.notesInfo([noteId]);
        const refreshedInfo = refreshedInfoResult as FieldGroupingNoteInfo[];
        if (!refreshedInfo || refreshedInfo.length === 0) {
          this.deps.showOsdNotification('Card not found');
          return;
        }

        const noteInfo = refreshedInfo[0]!;

        if (sentenceCardConfig.fieldGroupingMode === 'auto') {
          await this.deps.handleFieldGroupingAuto(
            duplicateNoteId,
            noteId,
            noteInfo,
            expressionText,
          );
          return;
        }
        // The manual workflow owns all user-facing notifications for its outcomes (cancelled,
        // unavailable, failed) — re-notifying on a false return here duplicated them.
        await this.deps.handleFieldGroupingManual(
          duplicateNoteId,
          noteId,
          noteInfo,
          expressionText,
        );
      });
    } catch (error) {
      log.error('Error triggering field grouping:', (error as Error).message);
      this.deps.showOsdNotification(`Field grouping failed: ${(error as Error).message}`);
    }
  }

  async buildFieldGroupingPreview(
    keepNoteId: number,
    deleteNoteId: number,
    deleteDuplicate: boolean,
  ): Promise<KikuMergePreviewResponse> {
    try {
      const notesInfoResult = await this.deps.notesInfo([keepNoteId, deleteNoteId]);
      const notesInfo = notesInfoResult as FieldGroupingNoteInfo[];
      const keepNoteInfo = notesInfo.find((note) => note.noteId === keepNoteId);
      const deleteNoteInfo = notesInfo.find((note) => note.noteId === deleteNoteId);

      if (!keepNoteInfo || !deleteNoteInfo) {
        return { ok: false, error: 'Could not load selected notes' };
      }

      const mergedFields = await this.deps.computeFieldGroupingMergedFields(
        keepNoteId,
        deleteNoteId,
        keepNoteInfo,
        deleteNoteInfo,
        false,
      );
      const keepBefore = this.deps.getNoteFieldMap(keepNoteInfo);
      const keepAfter = { ...keepBefore, ...mergedFields };
      const sourceBefore = this.deps.getNoteFieldMap(deleteNoteInfo);

      const compactFields: Record<string, string> = {};
      for (const fieldName of [
        'Sentence',
        'SentenceFurigana',
        'SentenceAudio',
        'Picture',
        'MiscInfo',
      ]) {
        const resolved = this.deps.resolveFieldName(Object.keys(keepAfter), fieldName);
        if (!resolved) continue;
        compactFields[fieldName] = keepAfter[resolved] || '';
      }

      return {
        ok: true,
        compact: {
          action: {
            keepNoteId,
            deleteNoteId,
            deleteDuplicate,
          },
          mergedFields: compactFields,
        },
        full: {
          keepNote: {
            id: keepNoteId,
            fieldsBefore: keepBefore,
          },
          sourceNote: {
            id: deleteNoteId,
            fieldsBefore: sourceBefore,
          },
          result: {
            fieldsAfter: keepAfter,
            wouldDeleteNoteId: deleteDuplicate ? deleteNoteId : null,
          },
        },
      };
    } catch (error) {
      return {
        ok: false,
        error: `Failed to build preview: ${(error as Error).message}`,
      };
    }
  }
}

function pickMostRecentDuplicateNoteId(
  duplicateNoteIds: number[],
  excludeNoteId: number,
): number | null {
  let bestNoteId: number | null = null;
  for (const noteId of duplicateNoteIds) {
    if (noteId === excludeNoteId) continue;
    if (bestNoteId === null || noteId > bestNoteId) {
      bestNoteId = noteId;
    }
  }
  return bestNoteId;
}
