import ctypes

import pytest

from psycopg import pq


@pytest.mark.parametrize(
    "command, status",
    [
        (b"", "EMPTY_QUERY"),
        (b"select 1", "TUPLES_OK"),
        (b"set timezone to utc", "COMMAND_OK"),
        (b"wat", "FATAL_ERROR"),
    ],
)
def test_status(pgconn, command, status):
    res = pgconn.exec_(command)
    assert res.status == getattr(pq.ExecStatus, status)
    assert status in repr(res)


def test_clear(pgconn):
    res = pgconn.exec_(b"select 1")
    assert res.status == pq.ExecStatus.TUPLES_OK
    res.clear()
    assert res.status == pq.ExecStatus.FATAL_ERROR
    res.clear()
    assert res.status == pq.ExecStatus.FATAL_ERROR


def test_pgresult_ptr(pgconn, libpq):
    res = pgconn.exec_(b"select 1")
    assert isinstance(res.pgresult_ptr, int)

    f = libpq.PQcmdStatus
    f.argtypes = [ctypes.c_void_p]
    f.restype = ctypes.c_char_p
    assert f(res.pgresult_ptr) == b"SELECT 1"

    res.clear()
    assert res.pgresult_ptr is None


def test_error_message(pgconn):
    res = pgconn.exec_(b"select 1")
    assert res.error_message == b""
    res = pgconn.exec_(b"select wat")
    assert b"wat" in res.error_message
    res.clear()
    assert res.error_message == b""


def test_get_error_message(pgconn):
    res = pgconn.exec_(b"select 1")
    assert res.get_error_message() == "no error details available"
    res = pgconn.exec_(b"select wat")
    assert "wat" in res.get_error_message()
    res.clear()
    assert res.get_error_message() == "no error details available"


def test_error_field(pgconn):
    res = pgconn.exec_(b"select wat")
    # https://github.com/cockroachdb/cockroach/issues/81794
    assert (
        res.error_field(pq.DiagnosticField.SEVERITY_NONLOCALIZED)
        or res.error_field(pq.DiagnosticField.SEVERITY)
    ) == b"ERROR"
    assert res.error_field(pq.DiagnosticField.SQLSTATE) == b"42703"
    assert b"wat" in res.error_field(pq.DiagnosticField.MESSAGE_PRIMARY)
    res.clear()
    assert res.error_field(pq.DiagnosticField.MESSAGE_PRIMARY) is None


@pytest.mark.parametrize("n", range(4))
def test_ntuples(pgconn, n):
    res = pgconn.exec_params(b"select generate_series(1, $1)", [str(n).encode("ascii")])
    assert res.ntuples == n
    res.clear()
    assert res.ntuples == 0


def test_nfields(pgconn):
    res = pgconn.exec_(b"select wat")
    assert res.nfields == 0
    res = pgconn.exec_(b"select 1, 2, 3")
    assert res.nfields == 3
    res.clear()
    assert res.nfields == 0


def test_fname(pgconn):
    res = pgconn.exec_(b'select 1 as foo, 2 as "BAR"')
    assert res.fname(0) == b"foo"
    assert res.fname(1) == b"BAR"
    assert res.fname(2) is None
    assert res.fname(-1) is None
    res.clear()
    assert res.fname(0) is None


@pytest.mark.crdb("skip", reason="ftable")
def test_ftable_and_col(pgconn):
    res = pgconn.exec_(b"""
        drop table if exists t1, t2;
        create table t1 as select 1 as f1;
        create table t2 as select 2 as f2, 3 as f3;
        """)
    assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message

    res = pgconn.exec_(
        b"select f1, f3, 't1'::regclass::oid, 't2'::regclass::oid from t1, t2"
    )
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message

    assert res.ftable(0) == int(res.get_value(0, 2).decode("ascii"))
    assert res.ftable(1) == int(res.get_value(0, 3).decode("ascii"))
    assert res.ftablecol(0) == 1
    assert res.ftablecol(1) == 2
    res.clear()
    assert res.ftable(0) == 0
    assert res.ftablecol(0) == 0


@pytest.mark.parametrize("fmt", (0, 1))
def test_fformat(pgconn, fmt):
    res = pgconn.exec_params(b"select 1", [], result_format=fmt)
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message
    assert res.fformat(0) == fmt
    assert res.binary_tuples == fmt
    res.clear()
    assert res.fformat(0) == 0
    assert res.binary_tuples == 0


def test_ftype(pgconn):
    res = pgconn.exec_(b"select 1::int4, 1::numeric, 1::text")
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message
    assert res.ftype(0) == 23
    assert res.ftype(1) == 1700
    assert res.ftype(2) == 25
    res.clear()
    assert res.ftype(0) == 0


def test_fmod(pgconn):
    res = pgconn.exec_(b"select 1::int, 1::numeric(10), 1::numeric(10,2)")
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message
    assert res.fmod(0) == -1
    assert res.fmod(1) == 0xA0004
    assert res.fmod(2) == 0xA0006
    res.clear()
    assert res.fmod(0) == 0


def test_fsize(pgconn):
    res = pgconn.exec_(b"select 1::int4, 1::bigint, 1::text")
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message
    assert res.fsize(0) == 4
    assert res.fsize(1) == 8
    assert res.fsize(2) == -1
    res.clear()
    assert res.fsize(0) == 0


def test_get_value(pgconn):
    res = pgconn.exec_(b"select 'a', '', NULL")
    assert res.status == pq.ExecStatus.TUPLES_OK, res.error_message
    assert res.get_value(0, 0) == b"a"
    assert res.get_value(0, 1) == b""
    assert res.get_value(0, 2) is None
    res.clear()
    assert res.get_value(0, 0) is None


def test_nparams_types(pgconn):
    res = pgconn.prepare(b"", b"select $1::int4, $2::text")
    assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message

    res = pgconn.describe_prepared(b"")
    assert res.status == pq.ExecStatus.COMMAND_OK, res.error_message

    assert res.nparams == 2
    assert res.param_type(0) == 23
    assert res.param_type(1) == 25

    res.clear()
    assert res.nparams == 0
    assert res.param_type(0) == 0


def test_command_status(pgconn):
    res = pgconn.exec_(b"select 1")
    assert res.command_status == b"SELECT 1"
    res = pgconn.exec_(b"set timezone to utc")
    assert res.command_status == b"SET"
    res.clear()
    assert res.command_status is None


def test_command_tuples(pgconn):
    res = pgconn.exec_(b"set timezone to utf8")
    assert res.command_tuples is None
    res = pgconn.exec_(b"select * from generate_series(1, 10)")
    assert res.command_tuples == 10
    res.clear()
    assert res.command_tuples is None


def test_oid_value(pgconn):
    res = pgconn.exec_(b"select 1")
    assert res.oid_value == 0
    res.clear()
    assert res.oid_value == 0
