import { Sequelize, QueryTypes } from 'sequelize';

export class OidcAdapterService {
	constructor(private readonly sequelize: Sequelize) {}

	createAdapter(name: string) {
		const db = this.sequelize;

		class PgOidcAdapter {
			async upsert(id: string, payload: unknown, expiresIn: number): Promise<void> {
				await db.query(
					`INSERT INTO oidc_grants(model, id, payload, expires_at)
           VALUES(:name, :id, :payload::jsonb, NOW() + (:expiresIn || ' seconds')::interval)
           ON CONFLICT(model, id)
           DO UPDATE SET payload = :payload::jsonb, expires_at = NOW() + (:expiresIn || ' seconds')::interval`,
					{
						replacements: { name, id, payload: JSON.stringify(payload), expiresIn },
						type: QueryTypes.UPDATE,
					},
				);
			}

			async find(id: string): Promise<unknown> {
				const rows = await db.query<{ payload: unknown }>(
					`SELECT payload
           FROM oidc_grants
           WHERE model = :name
             AND id = :id
             AND (expires_at IS NULL OR expires_at > NOW())`,
					{
						replacements: { name, id },
						type: QueryTypes.SELECT,
					},
				);
				return rows[0]?.payload;
			}

			async findByUserCode(userCode: string): Promise<unknown> {
				const rows = await db.query<{ payload: unknown }>(
					`SELECT payload
           FROM oidc_grants
           WHERE model = :name
             AND payload->>'userCode' = :userCode
             AND (expires_at IS NULL OR expires_at > NOW())`,
					{
						replacements: { name, userCode },
						type: QueryTypes.SELECT,
					},
				);
				return rows[0]?.payload;
			}

			async findByUid(uid: string): Promise<unknown> {
				const rows = await db.query<{ payload: unknown }>(
					`SELECT payload
           FROM oidc_grants
           WHERE model = :name
             AND payload->>'uid' = :uid
             AND (expires_at IS NULL OR expires_at > NOW())`,
					{
						replacements: { name, uid },
						type: QueryTypes.SELECT,
					},
				);
				return rows[0]?.payload;
			}

			async destroy(id: string): Promise<void> {
				await db.query('DELETE FROM oidc_grants WHERE model = :name AND id = :id', {
					replacements: { name, id },
					type: QueryTypes.DELETE,
				});
			}

			async revokeByGrantId(grantId: string): Promise<void> {
				await db.query("DELETE FROM oidc_grants WHERE payload->>'grantId' = :grantId", {
					replacements: { grantId },
					type: QueryTypes.DELETE,
				});
			}

			async consume(id: string): Promise<void> {
				await db.query(
					`UPDATE oidc_grants
           SET payload = jsonb_set(payload, '{consumed}', to_jsonb(EXTRACT(EPOCH FROM NOW())::bigint))
           WHERE model = :name AND id = :id`,
					{
						replacements: { name, id },
						type: QueryTypes.UPDATE,
					},
				);
			}
		}

		return new PgOidcAdapter();
	}
}
