/**
 * VersionService — CRUD operations for repository_versions.
 *
 * Operates directly on the raw better-sqlite3 client for synchronous queries,
 * matching the pattern used by RepositoryService.
 */

import type Database from 'better-sqlite3';
import type { RepositoryVersion } from '$lib/types';
import { AlreadyExistsError, NotFoundError } from '$lib/server/utils/validation';

export class VersionService {
	constructor(private readonly db: Database.Database) {}

	/**
	 * List all versions for a repository, newest first.
	 */
	list(repositoryId: string): RepositoryVersion[] {
		return this.db
			.prepare(
				`SELECT * FROM repository_versions
         WHERE repository_id = ?
         ORDER BY created_at DESC`
			)
			.all(repositoryId) as RepositoryVersion[];
	}

	/**
	 * Add a new version record for a repository.
	 * The version ID follows the convention: {repositoryId}/{tag}
	 *
	 * @throws NotFoundError      when the parent repository does not exist
	 * @throws AlreadyExistsError when the tag is already registered
	 */
	add(repositoryId: string, tag: string, title?: string): RepositoryVersion {
		// Verify parent repository exists.
		const repo = this.db
			.prepare(`SELECT id FROM repositories WHERE id = ?`)
			.get(repositoryId) as { id: string } | undefined;

		if (!repo) {
			throw new NotFoundError(`Repository ${repositoryId} not found`);
		}

		const id = `${repositoryId}/${tag}`;

		// Check for collision.
		const existing = this.getByTag(repositoryId, tag);
		if (existing) {
			throw new AlreadyExistsError(`Version ${tag} already exists for repository ${repositoryId}`);
		}

		const now = Math.floor(Date.now() / 1000);

		this.db
			.prepare(
				`INSERT INTO repository_versions
         (id, repository_id, tag, title, state, total_snippets, indexed_at, created_at)
         VALUES (?, ?, ?, ?, 'pending', 0, NULL, ?)`
			)
			.run(id, repositoryId, tag, title ?? null, now);

		return this.db
			.prepare(`SELECT * FROM repository_versions WHERE id = ?`)
			.get(id) as RepositoryVersion;
	}

	/**
	 * Delete a version and all associated documents / snippets (cascades via FK).
	 *
	 * @throws NotFoundError when the version does not exist
	 */
	remove(repositoryId: string, tag: string): void {
		const version = this.getByTag(repositoryId, tag);
		if (!version) {
			throw new NotFoundError(`Version ${tag} not found for repository ${repositoryId}`);
		}

		this.db
			.prepare(`DELETE FROM repository_versions WHERE repository_id = ? AND tag = ?`)
			.run(repositoryId, tag);
	}

	/**
	 * Get a single version by tag.
	 * Returns `null` when not found.
	 */
	getByTag(repositoryId: string, tag: string): RepositoryVersion | null {
		return (
			(this.db
				.prepare(
					`SELECT * FROM repository_versions WHERE repository_id = ? AND tag = ?`
				)
				.get(repositoryId, tag) as RepositoryVersion | undefined) ?? null
		);
	}

	/**
	 * Register multiple versions from a trueref.json `previousVersions` array.
	 * Silently skips tags that are already registered (idempotent).
	 * All new records are created with state = 'pending'.
	 *
	 * @throws NotFoundError when the parent repository does not exist
	 */
	registerFromConfig(
		repositoryId: string,
		previousVersions: { tag: string; title: string }[]
	): RepositoryVersion[] {
		// Verify parent repository exists.
		const repo = this.db
			.prepare(`SELECT id FROM repositories WHERE id = ?`)
			.get(repositoryId) as { id: string } | undefined;

		if (!repo) {
			throw new NotFoundError(`Repository ${repositoryId} not found`);
		}

		const registered: RepositoryVersion[] = [];

		for (const { tag, title } of previousVersions) {
			const existing = this.getByTag(repositoryId, tag);
			if (existing) {
				// Already registered — skip silently.
				registered.push(existing);
				continue;
			}

			const version = this.add(repositoryId, tag, title);
			registered.push(version);
		}

		return registered;
	}
}
