import type { StorageAdapter } from '@mosaicstack/storage';
import type {
  MemoryAdapter,
  MemoryConfig,
  NewInsight,
  Insight,
  InsightSearchResult,
} from '../types.js';
import type { EmbeddingProvider } from '../vector-store.js';

type KeywordConfig = Extract<MemoryConfig, { type: 'keyword' }>;

const PREFERENCES = 'preferences';
const INSIGHTS = 'insights';

type PreferenceRecord = Record<string, unknown> & {
  id: string;
  userId: string;
  key: string;
  value: unknown;
  category: string;
};

type InsightRecord = Record<string, unknown> & {
  id: string;
  userId: string;
  content: string;
  source: string;
  category: string;
  relevanceScore: number;
  metadata: Record<string, unknown>;
  createdAt: string;
  updatedAt?: string;
  decayedAt?: string;
};

export class KeywordAdapter implements MemoryAdapter {
  readonly name = 'keyword';
  readonly embedder: EmbeddingProvider | null = null;

  private storage: StorageAdapter;

  constructor(config: KeywordConfig) {
    this.storage = config.storage;
  }

  /* ------------------------------------------------------------------ */
  /*  Preferences                                                       */
  /* ------------------------------------------------------------------ */

  async getPreference(userId: string, key: string): Promise<unknown | null> {
    const row = await this.storage.findOne<PreferenceRecord>(PREFERENCES, { userId, key });
    return row?.value ?? null;
  }

  async setPreference(
    userId: string,
    key: string,
    value: unknown,
    category?: string,
  ): Promise<void> {
    const existing = await this.storage.findOne<PreferenceRecord>(PREFERENCES, { userId, key });
    if (existing) {
      await this.storage.update(PREFERENCES, existing.id, {
        value,
        ...(category !== undefined ? { category } : {}),
      });
    } else {
      await this.storage.create(PREFERENCES, {
        userId,
        key,
        value,
        category: category ?? 'general',
      });
    }
  }

  async deletePreference(userId: string, key: string): Promise<boolean> {
    const existing = await this.storage.findOne<PreferenceRecord>(PREFERENCES, { userId, key });
    if (!existing) return false;
    return this.storage.delete(PREFERENCES, existing.id);
  }

  async listPreferences(
    userId: string,
    category?: string,
  ): Promise<Array<{ key: string; value: unknown; category: string }>> {
    const filter: Record<string, unknown> = { userId };
    if (category !== undefined) filter.category = category;

    const rows = await this.storage.find<PreferenceRecord>(PREFERENCES, filter);
    return rows.map((r) => ({ key: r.key, value: r.value, category: r.category }));
  }

  /* ------------------------------------------------------------------ */
  /*  Insights                                                          */
  /* ------------------------------------------------------------------ */

  async storeInsight(insight: NewInsight): Promise<Insight> {
    const now = new Date();
    const row = await this.storage.create<Record<string, unknown>>(INSIGHTS, {
      userId: insight.userId,
      content: insight.content,
      source: insight.source,
      category: insight.category,
      relevanceScore: insight.relevanceScore,
      metadata: insight.metadata ?? {},
      createdAt: now.toISOString(),
    });

    return {
      id: row.id,
      userId: insight.userId,
      content: insight.content,
      source: insight.source,
      category: insight.category,
      relevanceScore: insight.relevanceScore,
      metadata: insight.metadata,
      createdAt: now,
    };
  }

  async getInsight(id: string): Promise<Insight | null> {
    const row = await this.storage.read<InsightRecord>(INSIGHTS, id);
    if (!row) return null;
    return toInsight(row);
  }

  async searchInsights(
    userId: string,
    query: string,
    opts?: { limit?: number; embedding?: number[] },
  ): Promise<InsightSearchResult[]> {
    const limit = opts?.limit ?? 10;
    const normalizedQuery = query.trim();
    const matchAll = normalizedQuery === '*';
    const words = matchAll
      ? []
      : normalizedQuery
          .toLowerCase()
          .split(/\s+/)
          .filter((word) => word.length > 0);

    if (words.length === 0 && !matchAll) return [];

    const rows = await this.storage.find<InsightRecord>(INSIGHTS, { userId });

    const scored: InsightSearchResult[] = [];
    for (const row of rows) {
      const content = row.content.toLowerCase();
      let score = matchAll ? 1 : 0;
      for (const word of words) {
        if (content.includes(word)) score++;
      }
      if (score > 0) {
        scored.push({
          id: row.id,
          content: row.content,
          score,
          metadata: row.metadata ?? undefined,
        });
      }
    }

    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, limit);
  }

  async deleteInsight(id: string): Promise<boolean> {
    return this.storage.delete(INSIGHTS, id);
  }

  /* ------------------------------------------------------------------ */
  /*  Lifecycle                                                         */
  /* ------------------------------------------------------------------ */

  async close(): Promise<void> {
    // no-op — storage adapter manages its own lifecycle
  }
}

/* ------------------------------------------------------------------ */
/*  Helpers                                                            */
/* ------------------------------------------------------------------ */

function toInsight(row: InsightRecord): Insight {
  return {
    id: row.id,
    userId: row.userId,
    content: row.content,
    source: row.source,
    category: row.category,
    relevanceScore: row.relevanceScore,
    metadata: row.metadata ?? undefined,
    createdAt: new Date(row.createdAt),
    updatedAt: row.updatedAt ? new Date(row.updatedAt) : undefined,
    decayedAt: row.decayedAt ? new Date(row.decayedAt) : undefined,
  };
}
