import { _getAdapter, intBool, parseJsonObject } from './sf-db-core.js';
import { SF_STALE_STATE, SFError } from '../errors.js';
import { logWarning } from '../workflow-logger.js';

export function getActiveMemories({ category, limit = 200 } = {}) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	const rows = category
		? currentDb
				.prepare(
					"SELECT * FROM active_memories WHERE category = ? ORDER BY updated_at DESC LIMIT ?",
				)
				.all(category, limit)
		: currentDb
				.prepare(
					"SELECT * FROM active_memories ORDER BY updated_at DESC LIMIT ?",
				)
				.all(limit);
	return rows.map((r) => ({
		id: r["id"],
		category: r["category"],
		content: r["content"],
		confidence: r["confidence"],
		sourceUnitId: r["source_unit_id"],
		tags: (() => {
			try {
				return JSON.parse(r["tags"] ?? "[]");
			} catch {
				return [];
			}
		})(),
		createdAt: r["created_at"],
		updatedAt: r["updated_at"],
	}));
}

export function insertMemoryRow(args) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at, tags)
     VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at, :tags)`)
		.run({
			":id": args.id,
			":category": args.category,
			":content": args.content,
			":confidence": args.confidence,
			":source_unit_type": args.sourceUnitType,
			":source_unit_id": args.sourceUnitId,
			":created_at": args.createdAt,
			":updated_at": args.updatedAt,
			":tags": JSON.stringify(args.tags ?? []),
		});
}

export function rewriteMemoryId(placeholderId, realId) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare("UPDATE memories SET id = :real_id WHERE id = :placeholder")
		.run({
			":real_id": realId,
			":placeholder": placeholderId,
		});
}

export function updateMemoryContentRow(id, content, confidence, updatedAt) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	if (confidence != null) {
		currentDb
			.prepare(
				"UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id",
			)
			.run({
				":content": content,
				":confidence": confidence,
				":updated_at": updatedAt,
				":id": id,
			});
	} else {
		currentDb
			.prepare(
				"UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id",
			)
			.run({ ":content": content, ":updated_at": updatedAt, ":id": id });
	}
}

export function incrementMemoryHitCount(id, updatedAt) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(
			"UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id",
		)
		.run({ ":updated_at": updatedAt, ":id": id });
}

export function supersedeMemoryRow(oldId, newId, updatedAt) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(
			"UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id",
		)
		.run({ ":new_id": newId, ":updated_at": updatedAt, ":old_id": oldId });
}

export function markMemoryUnitProcessed(unitKey, activityFile, processedAt) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
     VALUES (:key, :file, :at)`)
		.run({ ":key": unitKey, ":file": activityFile, ":at": processedAt });
}

export function decayMemoriesBefore(cutoffTs, now) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`UPDATE memories
     SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
     WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`)
		.run({ ":now": now, ":cutoff": cutoffTs });
}

export function expireStaleMemories(unstartedTtlDays = 28, maxTtlDays = 90) {
	const currentDb = _getAdapter();
	if (!currentDb) return 0;
	const now = new Date().toISOString();
	const cutoffUnstarted = new Date(
		Date.now() - unstartedTtlDays * 86_400_000,
	).toISOString();
	const cutoffMax = new Date(
		Date.now() - maxTtlDays * 86_400_000,
	).toISOString();
	const result = currentDb
		.prepare(`UPDATE memories SET superseded_by = 'ttl-expired', updated_at = :now
     WHERE superseded_by IS NULL
     AND (
       (hit_count = 0 AND updated_at < :cutoff_unstarted)
       OR updated_at < :cutoff_max
     )`)
		.run({
			":now": now,
			":cutoff_unstarted": cutoffUnstarted,
			":cutoff_max": cutoffMax,
		});
	return result.changes ?? 0;
}

export function supersedeLowestRankedMemories(limit, now) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
     WHERE id IN (
       SELECT id FROM memories
       WHERE superseded_by IS NULL
       ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
       LIMIT :limit
     )`)
		.run({ ":now": now, ":limit": limit });
}

export function insertMemorySourceRow(args) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`INSERT OR IGNORE INTO memory_sources (id, kind, uri, title, content, content_hash, imported_at, scope, tags)
     VALUES (:id, :kind, :uri, :title, :content, :content_hash, :imported_at, :scope, :tags)`)
		.run({
			":id": args.id,
			":kind": args.kind,
			":uri": args.uri,
			":title": args.title,
			":content": args.content,
			":content_hash": args.contentHash,
			":imported_at": args.importedAt,
			":scope": args.scope ?? "project",
			":tags": JSON.stringify(args.tags ?? []),
		});
}

export function deleteMemorySourceRow(id) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	const res = currentDb
		.prepare("DELETE FROM memory_sources WHERE id = :id")
		.run({ ":id": id });
	return (res?.changes ?? 0) > 0;
}

export function insertJudgment(entry) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	try {
		currentDb
			.prepare(`INSERT INTO judgments (unit_id, decision, alternatives_json, reasoning, confidence, ts)
       VALUES (:unit_id, :decision, :alternatives_json, :reasoning, :confidence, :ts)`)
			.run({
				":unit_id": entry.unitId ?? "",
				":decision": entry.decision ?? "",
				":alternatives_json": JSON.stringify(entry.alternatives ?? []),
				":reasoning": entry.reasoning ?? "",
				":confidence": entry.confidence ?? "medium",
				":ts": entry.ts ?? new Date().toISOString(),
			});
	} catch {
		// Judgment logging is best-effort
	}
}

export function getJudgmentsForUnit(unitIdPrefix, limit = 1000) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	try {
		const rows = currentDb
			.prepare(
				`SELECT id, unit_id AS unitId, decision, alternatives_json AS alternativesJson, reasoning, confidence, ts
         FROM judgments
         WHERE unit_id LIKE :prefix
         ORDER BY ts DESC
         LIMIT :limit`,
			)
			.all({
				":prefix": `${unitIdPrefix}%`,
				":limit": limit,
			});
		return rows.map((r) => ({
			id: r.id,
			unitId: r.unitId,
			decision: r.decision,
			alternatives: parseJsonObject(r.alternativesJson, []),
			reasoning: r.reasoning,
			confidence: r.confidence,
			ts: r.ts,
		}));
	} catch {
		return [];
	}
}

export function insertRetrievalEvidence(args) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	const now = args.recordedAt ?? new Date().toISOString();
	currentDb
		.prepare(`INSERT INTO retrieval_evidence (
        backend, source_kind, query, strategy, scope, project_root,
        git_head, git_branch, worktree_dirty, freshness, status,
        hit_count, elapsed_ms, cache_path, error, result_json, recorded_at
      ) VALUES (
        :backend, :source_kind, :query, :strategy, :scope, :project_root,
        :git_head, :git_branch, :worktree_dirty, :freshness, :status,
        :hit_count, :elapsed_ms, :cache_path, :error, :result_json, :recorded_at
      )`)
		.run({
			":backend": args.backend,
			":source_kind": args.sourceKind ?? "code",
			":query": args.query ?? "",
			":strategy": args.strategy ?? "",
			":scope": args.scope ?? "",
			":project_root": args.projectRoot ?? "",
			":git_head": args.gitHead ?? null,
			":git_branch": args.gitBranch ?? null,
			":worktree_dirty": intBool(args.worktreeDirty),
			":freshness": args.freshness ?? "unknown",
			":status": args.status ?? "ok",
			":hit_count": args.hitCount ?? 0,
			":elapsed_ms": args.elapsedMs ?? 0,
			":cache_path": args.cachePath ?? null,
			":error": args.error ?? null,
			":result_json": JSON.stringify(args.result ?? {}),
			":recorded_at": now,
		});
}

export function getRetrievalEvidence(limit = 100) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	const rows = currentDb
		.prepare(`SELECT
        id, backend, source_kind AS sourceKind, query, strategy, scope,
        project_root AS projectRoot, git_head AS gitHead,
        git_branch AS gitBranch, worktree_dirty AS worktreeDirty,
        freshness, status, hit_count AS hitCount, elapsed_ms AS elapsedMs,
        cache_path AS cachePath, error, result_json AS resultJson, recorded_at AS recordedAt
      FROM retrieval_evidence
      ORDER BY recorded_at DESC, id DESC
      LIMIT :limit`)
		.all({ ":limit": limit });
	return rows.map((row) => ({
		...row,
		worktreeDirty: row.worktreeDirty === 1,
		result: parseJsonObject(row.resultJson, {}),
	}));
}

export function upsertMemoryEmbedding(args) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	currentDb
		.prepare(`INSERT INTO memory_embeddings (memory_id, model, dim, vector, updated_at)
     VALUES (:memory_id, :model, :dim, :vector, :updated_at)
     ON CONFLICT(memory_id) DO UPDATE SET
       model = excluded.model,
       dim = excluded.dim,
       vector = excluded.vector,
       updated_at = excluded.updated_at`)
		.run({
			":memory_id": args.memoryId,
			":model": args.model,
			":dim": args.dim,
			":vector": args.vector,
			":updated_at": args.updatedAt,
		});
}

export function deleteMemoryEmbedding(memoryId) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	const res = currentDb
		.prepare("DELETE FROM memory_embeddings WHERE memory_id = :id")
		.run({ ":id": memoryId });
	return (res?.changes ?? 0) > 0;
}
