import { FindOptionsWhere, Repository, SelectQueryBuilder } from 'typeorm';
import { NetaClawMemoryEntity } from '../entity/memory.js';
import { MemoryProvider, MemoryEntry, MemorySearchOpts, MemoryPageOpts, MemoryPageResult, MemoryCountOpts, MemoryType } from './provider.js';

function toEntry(e: NetaClawMemoryEntity): MemoryEntry {
  return {
    id: e.id,
    agentName: e.agentName,
    userId: e.userId,
    type: e.type as MemoryEntry['type'],
    name: e.name,
    content: e.content,
    description: e.description,
    metadata: e.metadata,
    createdAt: e.createTime,
    updatedAt: e.updateTime,
  };
}

export class MysqlMemoryProvider implements MemoryProvider {
  private indexEnsured = false;

  constructor(private repo: Repository<NetaClawMemoryEntity>) {}

  /** TypeORM synchronize 会删掉手动加的 FULLTEXT 索引，每次首次使用时确保存在 */
  private async ensureFulltextIndex(): Promise<void> {
    if (this.indexEnsured) return;
    try {
      await this.repo.query(
        `ALTER TABLE netaclaw_memory ADD FULLTEXT INDEX ft_content (name, content, description) WITH PARSER ngram`
      );
    } catch (e: any) {
      // 索引已存在则忽略 (ER_DUP_KEYNAME)
      if (!e.message?.includes('Duplicate')) throw e;
    }
    this.indexEnsured = true;
  }

  async save(entry: Omit<MemoryEntry, 'id' | 'createdAt' | 'updatedAt'>): Promise<MemoryEntry> {
    const saved = await this.repo.save({
      agentName: entry.agentName,
      userId: entry.userId,
      type: entry.type,
      name: entry.name,
      content: entry.content,
      description: entry.description ?? '',
      metadata: entry.metadata,
    });
    return toEntry(saved);
  }

  async update(id: number, partial: Partial<Pick<MemoryEntry, 'name' | 'content' | 'description' | 'type' | 'metadata'>>): Promise<MemoryEntry> {
    await this.repo.update(id, partial);
    const updated = await this.repo.findOneByOrFail({ id });
    return toEntry(updated);
  }

  async delete(id: number): Promise<void> {
    await this.repo.delete(id);
  }

  async search(query: string, opts: MemorySearchOpts): Promise<MemoryEntry[]> {
    await this.ensureFulltextIndex();
    const limit = opts.limit ?? 10;
    let qb = this.repo.createQueryBuilder('m')
      .where('m.agentName = :agentName', { agentName: opts.agentName })
      .andWhere(`MATCH(name, content, description) AGAINST(:query IN BOOLEAN MODE)`, { query })
      .orderBy(`MATCH(name, content, description) AGAINST(:query IN BOOLEAN MODE)`, 'DESC')
      .limit(limit);

    if (opts.userId) {
      qb = qb.andWhere('m.userId = :userId', { userId: opts.userId });
    }

    if (opts.type) {
      qb = qb.andWhere('m.type = :type', { type: opts.type });
    }

    const results = await qb.getMany();
    return results.map(toEntry);
  }

  async list(opts: MemorySearchOpts): Promise<MemoryEntry[]> {
    const where: FindOptionsWhere<NetaClawMemoryEntity> = { agentName: opts.agentName };
    if (opts.userId) where.userId = opts.userId;
    if (opts.type) where.type = opts.type;
    const results = await this.repo.find({
      where,
      order: { updateTime: 'DESC' },
      take: opts.limit ?? 10,
    });
    return results.map(toEntry);
  }

  async getById(id: number): Promise<MemoryEntry | null> {
    const e = await this.repo.findOneBy({ id });
    return e ? toEntry(e) : null;
  }

  private applyFilters(
    qb: SelectQueryBuilder<NetaClawMemoryEntity>,
    opts: { agentName?: string; userId?: string; type?: MemoryType; keyWord?: string }
  ): SelectQueryBuilder<NetaClawMemoryEntity> {
    if (opts.agentName) qb.andWhere('m.agentName = :agentName', { agentName: opts.agentName });
    if (opts.userId) qb.andWhere('m.userId = :userId', { userId: opts.userId });
    if (opts.type) qb.andWhere('m.type = :type', { type: opts.type });
    if (opts.keyWord) {
      qb.andWhere('(m.name LIKE :kw OR m.content LIKE :kw OR m.description LIKE :kw)', { kw: `%${opts.keyWord}%` });
    }
    return qb;
  }

  async page(opts: MemoryPageOpts): Promise<MemoryPageResult> {
    const page = Math.max(1, opts.page || 1);
    const size = Math.max(1, Math.min(200, opts.size || 20));
    const qb = this.applyFilters(this.repo.createQueryBuilder('m'), opts)
      .orderBy('m.updateTime', 'DESC')
      .addOrderBy('m.id', 'DESC');
    const total = await qb.getCount();
    const list = await qb.skip((page - 1) * size).take(size).getMany();
    return { list: list.map(toEntry), pagination: { page, size, total } };
  }

  async count(opts: MemoryCountOpts): Promise<number> {
    const qb = this.applyFilters(this.repo.createQueryBuilder('m'), opts);
    return qb.getCount();
  }
}
