import {QueryResult, QueryResultRow} from "pg"
import {UsersTable, UsersTableImpl} from "./users.js"
import {CharacterCreationTable, CharacterCreationTableImpl} from "./character_creation.js"

export interface PoolLike {
    connect(): Promise<PoolClientLike>
}

export interface PoolClientLike extends Queryable {
    release(err?: boolean): void
}

export function makeTransactable(p: PoolLike): Transactable {
    return async function transactable(callback: (q: QueryType, attempts: number) => Promise<void>): Promise<void> {
        const client = await p.connect()
        const query = client.query.bind(client)
        let committed = false
        let attempts = 0
        while (!committed) {
            try {
                await client.query("BEGIN")
            } catch (err) {
                client.release()
                throw err
            }
            try {
                await callback(query, attempts)
            } catch (err) {
                try {
                    await client.query("ROLLBACK")
                    client.release()
                } catch (err) {
                    client.release(true)
                }
                throw err
            }
            try {
                await client.query("COMMIT")
                committed = true
            } catch (err) {
                attempts += 1
            }
        }
        client.release()
    }
}

export interface Database {
    readonly users: UsersTable
    readonly characterCreation: CharacterCreationTable
}

export interface Queryable {
    query<RowT extends QueryResultRow>(queryText: string): Promise<QueryResult<RowT>>

    query<RowT extends QueryResultRow, ParamT extends any[]>(queryText: string,
                                                             params: ParamT): Promise<QueryResult<RowT>>
}

export type QueryType = Queryable["query"]
export type Transactable = (transaction: (client: QueryType, attempts: number) => Promise<void>) => Promise<void>

export class DatabaseImpl implements Database {
    readonly users: UsersTableImpl
    readonly characterCreation: CharacterCreationTableImpl

    constructor({query, transactable}: { query: QueryType, transactable: Transactable }) {
        this.users = new UsersTableImpl({query})
        this.characterCreation = new CharacterCreationTableImpl({query, transactable})
    }
}