import { readdir, readFile, mkdir, copyFile } from 'node:fs/promises';
import { join } from 'node:path';
import type { NoteRepository, ImportNoteInput } from '../repository/NoteRepository.js';
import type { MediaStore } from './MediaStore.js';
import { parseExportNote, type ParsedNote } from './importFormat.js';

export interface ImportPlan {
  total: number;
  newCount: number;
  unchangedCount: number;
  forkedCount: number;
  mediaCount: number;
}

export interface ImportResult extends ImportPlan {
  /** Map of original-export-id → final-DB-id (differs only for forked rows). */
  finalNoteIds: Map<string, string>;
}

function inferExtFromMime(mime: string): string {
  if (mime === 'image/png') return 'png';
  if (mime === 'image/jpeg') return 'jpg';
  if (mime === 'image/gif') return 'gif';
  if (mime === 'image/webp') return 'webp';
  return 'bin';
}

function parsedToInput(parsed: ParsedNote): ImportNoteInput {
  return {
    id: parsed.id,
    rawText: parsed.rawText,
    createdAt: parsed.createdAt,
    updatedAt: parsed.updatedAt,
    aiTitle: parsed.aiTitle,
    aiSummary: parsed.aiSummary,
    titleEditedByUser: parsed.titleEditedByUser,
    summaryEditedByUser: parsed.summaryEditedByUser,
    aiProvider: parsed.aiProvider,
    aiGeneratedAt: parsed.aiGeneratedAt,
    userIntent: parsed.userIntent,
    intentPromptedAt: parsed.intentPromptedAt,
    tags: parsed.tags,
    deletedAt: parsed.deletedAt
  };
}

export class ImportService {
  constructor(
    private repo: NoteRepository,
    private mediaStore: MediaStore
  ) {}

  async preview(sourceDir: string): Promise<ImportPlan> {
    const files = await this.scanNotes(sourceDir);
    const plan: ImportPlan = {
      total: 0,
      newCount: 0,
      unchangedCount: 0,
      forkedCount: 0,
      mediaCount: 0
    };
    for (const f of files) {
      const content = await readFile(f, 'utf8');
      const parsed = parseExportNote(content);
      plan.total += 1;
      const existing = this.repo.findRawTextById(parsed.id);
      if (existing === null) plan.newCount += 1;
      else if (existing === parsed.rawText) plan.unchangedCount += 1;
      else plan.forkedCount += 1;
      plan.mediaCount += parsed.images.length;
    }
    return plan;
  }

  async run(sourceDir: string): Promise<ImportResult> {
    const files = await this.scanNotes(sourceDir);
    const finalNoteIds = new Map<string, string>();
    let newCount = 0;
    let unchangedCount = 0;
    let forkedCount = 0;
    let mediaCount = 0;

    for (const f of files) {
      const content = await readFile(f, 'utf8');
      const parsed = parseExportNote(content);
      const r = this.repo.importNote(parsedToInput(parsed));
      finalNoteIds.set(parsed.id, r.id);

      if (r.status === 'inserted') newCount += 1;
      else if (r.status === 'skipped') unchangedCount += 1;
      else forkedCount += 1;

      // Skip media for already-present (skipped) notes — DB already has them.
      if (r.status === 'skipped') continue;

      // Copy media files into MediaStore convention <profileDir>/media/{noteId}/{n}.{ext}
      const noteMediaDir = join(this.mediaStore.absolutePath('media'), r.id);
      if (parsed.images.length > 0) {
        await mkdir(noteMediaDir, { recursive: true });
      }
      const mediaRows = [];
      for (let i = 0; i < parsed.images.length; i++) {
        const img = parsed.images[i]!;
        const src = join(sourceDir, img.rel);
        const ext = inferExtFromMime(img.mime);
        const dstFilename = `${i + 1}.${ext}`;
        const dstAbs = join(noteMediaDir, dstFilename);
        await copyFile(src, dstAbs);
        mediaRows.push({
          noteId: r.id,
          kind: 'image' as const,
          relPath: `media/${r.id}/${dstFilename}`,
          mime: img.mime,
          bytes: img.bytes
        });
        mediaCount += 1;
      }
      if (mediaRows.length > 0) {
        this.repo.insertMedia(mediaRows);
      }
    }

    return {
      total: files.length,
      newCount,
      unchangedCount,
      forkedCount,
      mediaCount,
      finalNoteIds
    };
  }

  async applySyncFromDir(dir: string): Promise<{ changedCount: number }> {
    const files = await this.scanNotes(dir);
    let changedCount = 0;
    for (const f of files) {
      const content = await readFile(f, 'utf8');
      const parsed = parseExportNote(content);
      const r = this.repo.upsertFromSync({
        id: parsed.id,
        rawText: parsed.rawText,
        createdAt: parsed.createdAt,
        updatedAt: parsed.updatedAt,
        aiTitle: parsed.aiTitle,
        aiSummary: parsed.aiSummary,
        titleEditedByUser: parsed.titleEditedByUser,
        summaryEditedByUser: parsed.summaryEditedByUser,
        aiProvider: parsed.aiProvider,
        aiGeneratedAt: parsed.aiGeneratedAt,
        userIntent: parsed.userIntent,
        intentPromptedAt: parsed.intentPromptedAt,
        tags: parsed.tags,
        status: parsed.status,
        statusChangedAt: parsed.statusChangedAt,
        moveReason: parsed.moveReason,
        dueDate: parsed.dueDate,
        dueDateEditedByUser: parsed.dueDateEditedByUser
      });
      if (r.status !== 'skipped') changedCount += 1;
    }
    return { changedCount };
  }

  private async scanNotes(sourceDir: string): Promise<string[]> {
    const notesDir = join(sourceDir, 'notes');
    let entries: string[];
    try {
      entries = await readdir(notesDir);
    } catch (err) {
      if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];
      throw err;
    }
    return entries
      .filter((e) => e.endsWith('.md'))
      .sort()
      .map((e) => join(notesDir, e));
  }
}
