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

export function upsertSession(entry) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	const now = new Date().toISOString();
	currentDb
		.prepare(`INSERT INTO sessions
       (session_id, trace_id, mode, cwd, repo, branch, summary, summary_count, created_at, updated_at)
       VALUES (:session_id, :trace_id, :mode, :cwd, :repo, :branch, :summary, 0, :now, :now)
       ON CONFLICT(session_id) DO UPDATE SET
         trace_id     = COALESCE(excluded.trace_id, sessions.trace_id),
         repo         = COALESCE(excluded.repo, sessions.repo),
         branch       = COALESCE(excluded.branch, sessions.branch),
         summary      = COALESCE(excluded.summary, sessions.summary),
         summary_count = CASE WHEN excluded.summary IS NOT NULL
                              THEN sessions.summary_count + 1
                              ELSE sessions.summary_count END,
         updated_at   = excluded.updated_at`)
		.run({
			":session_id": entry.sessionId,
			":trace_id": entry.traceId ?? null,
			":mode": entry.mode ?? "interactive",
			":cwd": entry.cwd ?? "",
			":repo": entry.repo ?? null,
			":branch": entry.branch ?? null,
			":summary": entry.summary ?? null,
			":now": now,
		});
}

export function archiveSession(sessionId) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	currentDb
		.prepare(
			`UPDATE sessions SET archived_at = :now, updated_at = :now WHERE session_id = :session_id`,
		)
		.run({ ":session_id": sessionId, ":now": new Date().toISOString() });
}

export function unarchiveSession(sessionId) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	currentDb
		.prepare(
			`UPDATE sessions SET archived_at = NULL, updated_at = :now WHERE session_id = :session_id`,
		)
		.run({ ":session_id": sessionId, ":now": new Date().toISOString() });
}

export function insertSessionTurn(entry) {
	const currentDb = _getAdapter();
	if (!currentDb) return null;
	const result = currentDb
		.prepare(`INSERT INTO turns
       (session_id, turn_index, user_message, assistant_response, ts)
       VALUES (:session_id, :turn_index, :user_message, :assistant_response, :ts)
       ON CONFLICT(session_id, turn_index) DO UPDATE SET
         user_message       = COALESCE(excluded.user_message, turns.user_message),
         assistant_response = COALESCE(excluded.assistant_response, turns.assistant_response)`)
		.run({
			":session_id": entry.sessionId,
			":turn_index": entry.turnIndex,
			":user_message": entry.userMessage ?? null,
			":assistant_response": entry.assistantResponse ?? null,
			":ts": entry.ts ?? new Date().toISOString(),
		});
	return result.lastInsertRowid ?? null;
}

export function patchTurnResponse(sessionId, turnIndex, assistantResponse) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	currentDb
		.prepare(`UPDATE turns SET assistant_response = :resp
       WHERE session_id = :sid AND turn_index = :idx AND assistant_response IS NULL`)
		.run({
			":resp": assistantResponse,
			":sid": sessionId,
			":idx": turnIndex,
		});
}

export function recordSessionFileTouch(entry) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	currentDb
		.prepare(`INSERT OR IGNORE INTO session_file_touches
       (session_id, path, tool_name, turn_id, first_seen_at)
       VALUES (:session_id, :path, :tool_name, :turn_id, :first_seen_at)`)
		.run({
			":session_id": entry.sessionId,
			":path": entry.path,
			":tool_name": entry.toolName ?? null,
			":turn_id": entry.turnId ?? null,
			":first_seen_at": entry.firstSeenAt ?? new Date().toISOString(),
		});
}

export function recordSessionRef(entry) {
	const currentDb = _getAdapter();
	if (!currentDb) return;
	currentDb
		.prepare(`INSERT OR IGNORE INTO session_refs
       (session_id, ref_type, ref_value, turn_id, created_at)
       VALUES (:session_id, :ref_type, :ref_value, :turn_id, :created_at)`)
		.run({
			":session_id": entry.sessionId,
			":ref_type": entry.refType,
			":ref_value": entry.refValue,
			":turn_id": entry.turnId ?? null,
			":created_at": entry.createdAt ?? new Date().toISOString(),
		});
}

export function searchSessionTurns(query, limit = 20) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	return currentDb
		.prepare(`SELECT t.id, t.session_id, t.turn_index, t.ts,
              t.user_message, t.assistant_response,
              s.mode, s.cwd, s.repo, s.branch
       FROM turns_fts
       JOIN turns t ON turns_fts.rowid = t.id
       JOIN sessions s ON t.session_id = s.session_id
       WHERE turns_fts MATCH :query
       ORDER BY rank
       LIMIT :limit`)
		.all({ ":query": query, ":limit": Math.max(1, Math.min(100, limit)) });
}

export function listRecentSessions(limit = 20) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	return currentDb
		.prepare(`SELECT s.session_id, s.mode, s.cwd, s.repo, s.branch,
              s.summary, s.created_at, s.updated_at,
              COUNT(DISTINCT t.id) AS turn_count,
              COUNT(DISTINCT f.id) AS file_count
       FROM sessions s
       LEFT JOIN turns t ON t.session_id = s.session_id
       LEFT JOIN session_file_touches f ON f.session_id = s.session_id
       GROUP BY s.session_id
       ORDER BY s.updated_at DESC
       LIMIT :limit`)
		.all({ ":limit": Math.max(1, Math.min(100, limit)) });
}

export function insertSessionSnapshot(args) {
	const currentDb = _getAdapter();
	if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open");
	const nextIndex = (() => {
		const row = currentDb
			.prepare(
				"SELECT COALESCE(MAX(snapshot_index), -1) + 1 AS nxt FROM session_snapshots WHERE session_id = :sid",
			)
			.get({ ":sid": args.sessionId });
		return row ? Number(row["nxt"]) : 0;
	})();
	currentDb
		.prepare(`INSERT INTO session_snapshots
       (session_id, snapshot_index, git_stash_ref, label, ts)
       VALUES (:sid, :idx, :ref, :label, :ts)`)
		.run({
			":sid": args.sessionId,
			":idx": nextIndex,
			":ref": args.gitStashRef ?? null,
			":label": args.label ?? null,
			":ts": args.ts ?? new Date().toISOString(),
		});
	const row = currentDb
		.prepare(
			"SELECT id FROM session_snapshots WHERE session_id = :sid AND snapshot_index = :idx",
		)
		.get({ ":sid": args.sessionId, ":idx": nextIndex });
	return row ? Number(row["id"]) : 0;
}

export function listSessionSnapshots(sessionId) {
	const currentDb = _getAdapter();
	if (!currentDb) return [];
	return currentDb
		.prepare(
			"SELECT * FROM session_snapshots WHERE session_id = :sid ORDER BY snapshot_index ASC",
		)
		.all({ ":sid": sessionId });
}
