#!/usr/bin/env python3
"""Firebirdsql wrapper inspired by subprocess.run."""
from datetime import datetime
from os import getenv
from pathlib import Path
from socket import getfqdn
from typing import NamedTuple

from firebirdsql import Connection, connect

FBTypes = str | float | datetime | None
Dataset = list[dict[str, FBTypes]]


class CompletedTransaction(NamedTuple):
    """The return value from execute(), representing a transaction that has finished."""

    host: str
    db: str
    user: str
    returncode: int
    error: str
    query: str
    params: tuple
    data: Dataset


class ExecuteError(Exception):
    """Exception raised for execute transaction errors."""


def connection(
    db: Path | str,
    host: str = "localhost",
    port: int = 3050,
    user: str = "TWUSER",
    passwd: str | None = None,
) -> Connection:
    """Create a connection to the database."""
    return connect(
        host=getfqdn(host),
        database=f"{db}",
        port=port,
        user=user,
        password=passwd or getenv("FIREBIRD_KEY"),
    )


def execute(
    query: str,
    params: tuple = (),
    db: Path | str = "",
    host: str = "localhost",
    port: int = 3050,
    user: str = "TWUSER",
    passwd: str | None = None,
    use_conn: Connection | None = None,
) -> CompletedTransaction:
    """
    Execute transaction.

    Run the transaction described by args.
    Wait for transaction to complete, then return a CompletedTransaction named tuple.

    An existing connection can be used with `use_conn`, it will not be closed after execution.
    """
    conn_success = False
    try:
        conn = (
            connection(host=host, db=db, port=port, user=user, passwd=passwd)
            if use_conn is None
            else use_conn
        )
        conn_success = True

        cur = conn.cursor()

        cur.execute(query=query, params=params)

        lines = cur.fetchall()
        columns = [f"{col[0]}".lower() for col in cur.description]
        conn.commit()

        if use_conn is None:
            conn.close()
    except Exception as e:  # noqa: BLE001
        data = []
        returncode = 1
        error = f"{e}"
    else:
        data = [dict(zip(columns, line, strict=True)) for line in lines]
        returncode = 0
        error = ""

    return CompletedTransaction(
        host=conn.hostname if conn_success else host,
        db=conn.filename if conn_success else db,
        user=conn.user if conn_success else user,
        returncode=returncode,
        error=error,
        query=query,
        params=params,
        data=data,
    )


def callproc(
    procname: str,
    params: tuple = (),
    db: Path | str = "",
    host: str = "localhost",
    port: int = 3050,
    user: str = "TWUSER",
    passwd: str | None = None,
    use_conn: Connection | None = None,
) -> CompletedTransaction:
    """Execute procedure with params."""
    query = f"EXECUTE PROCEDURE {procname} " + ",".join("?" * len(params))

    return execute(
        query=query,
        params=params,
        host=host,
        port=port,
        db=db,
        user=user,
        passwd=passwd,
        use_conn=use_conn,
    )
