import {QueryResult, QueryResultRow} from "pg"
import {UserTable, UserTableImpl} from "./users.js"
import {BattleTypeTable, BattleTypeTableImpl} from "./battle_types";
import {GenderTable, GenderTablesImpl} from "./genders";
import {CharacterTable, CharacterTableImpl} from "./characters";

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

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

export function makeTransact(p: PoolLike): TransactType {
    return async function transact({readonly=false, transaction}: {readonly?: boolean, transaction: (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 READ " + (readonly ? "ONLY" : "WRITE"))
            } catch (err) {
                client.release()
                throw err
            }
            try {
                await transaction(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 characters: CharacterTable
    readonly users: UserTable
    readonly genders: GenderTable
    readonly battleTypes: BattleTypeTable
}

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 TransactType = (config: {readonly?: boolean, transaction: (client: QueryType, attempts: number) => Promise<void>}) => Promise<void>

export class DatabaseImpl implements Database {
    readonly characters: CharacterTableImpl
    readonly users: UserTableImpl
    readonly genders: GenderTable
    readonly battleTypes: BattleTypeTableImpl

    constructor({query, transact}: { query: QueryType, transact: TransactType }) {
        this.characters = new CharacterTableImpl({transact})
        this.users = new UserTableImpl({query})
        this.genders = new GenderTablesImpl({transact})
        this.battleTypes = new BattleTypeTableImpl({transact})
    }
}