import logging
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Optional

import psycopg

logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class PostgresExecutionResult:
    rowcount: int
    status_message: str


class PostgresClient:
    def __init__(self, dsn: str):
        self.dsn = dsn
        self.connection: Optional[psycopg.Connection] = None

    def _get_connection(self) -> psycopg.Connection:
        if self.connection is None or self.connection.closed:
            try:
                self.connection = psycopg.connect(self.dsn)
            except Exception:
                logger.exception("Failed to connect to PostgreSQL")
                raise
        return self.connection

    def execute(self, query: str, params: Optional[Sequence] = None) -> PostgresExecutionResult:
        connection = self._get_connection()
        try:
            with connection.cursor() as cursor:
                cursor.execute(query, params)
                rowcount = cursor.rowcount
                status_message = cursor.statusmessage or ""
            connection.commit()
            return PostgresExecutionResult(rowcount=rowcount, status_message=status_message)
        except Exception:
            connection.rollback()
            logger.exception("PostgreSQL execute failed")
            raise

    def fetch_all(self, query: str, params: Optional[Sequence] = None) -> list[tuple]:
        connection = self._get_connection()
        try:
            with connection.cursor() as cursor:
                cursor.execute(query, params)
                return cursor.fetchall()
        except Exception:
            logger.exception("PostgreSQL fetch_all failed")
            raise

    def fetch_one(self, query: str, params: Optional[Sequence] = None) -> Optional[tuple]:
        connection = self._get_connection()
        try:
            with connection.cursor() as cursor:
                cursor.execute(query, params)
                return cursor.fetchone()
        except Exception:
            logger.exception("PostgreSQL fetch_one failed")
            raise

    def close(self) -> None:
        if self.connection is not None and not self.connection.closed:
            try:
                self.connection.close()
            except Exception:
                logger.exception("Failed to close PostgreSQL connection")
                raise

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
