import asyncio
import os

from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
from sqlalchemy.util import await_only

DB_HOST = os.environ.get('DB_HOST', 'localhost')  # for non-GCP environments
DB_PORT = os.environ.get('DB_PORT', '5432')  # for non-GCP environments
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASS = os.environ.get('DB_PASS', 'postgres').strip()
DB_NAME = os.environ.get('DB_NAME', 'openhands')

GCP_DB_INSTANCE = os.environ.get('GCP_DB_INSTANCE')  # for GCP environments
GCP_PROJECT = os.environ.get('GCP_PROJECT')
GCP_REGION = os.environ.get('GCP_REGION')

POOL_SIZE = int(os.environ.get('DB_POOL_SIZE', '25'))
MAX_OVERFLOW = int(os.environ.get('DB_MAX_OVERFLOW', '10'))


def _get_db_engine():
    if GCP_DB_INSTANCE:  # GCP environments

        def get_db_connection():
            from google.cloud.sql.connector import Connector

            connector = Connector()
            instance_string = f'{GCP_PROJECT}:{GCP_REGION}:{GCP_DB_INSTANCE}'
            return connector.connect(
                instance_string, 'pg8000', user=DB_USER, password=DB_PASS, db=DB_NAME
            )

        return create_engine(
            'postgresql+pg8000://',
            creator=get_db_connection,
            pool_size=POOL_SIZE,
            max_overflow=MAX_OVERFLOW,
            pool_pre_ping=True,
        )
    else:
        host_string = (
            f'postgresql+pg8000://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
        )
        return create_engine(
            host_string,
            pool_size=POOL_SIZE,
            max_overflow=MAX_OVERFLOW,
            pool_pre_ping=True,
        )


async def async_creator():
    from google.cloud.sql.connector import Connector

    loop = asyncio.get_running_loop()
    async with Connector(loop=loop) as connector:
        conn = await connector.connect_async(
            f'{GCP_PROJECT}:{GCP_REGION}:{GCP_DB_INSTANCE}',  # Cloud SQL instance connection name"
            'asyncpg',
            user=DB_USER,
            password=DB_PASS,
            db=DB_NAME,
        )
        return conn


def _get_async_db_engine():
    if GCP_DB_INSTANCE:  # GCP environments

        def adapted_creator():
            dbapi = engine.dialect.dbapi
            from sqlalchemy.dialects.postgresql.asyncpg import (
                AsyncAdapt_asyncpg_connection,
            )

            return AsyncAdapt_asyncpg_connection(
                dbapi,
                await_only(async_creator()),
                prepared_statement_cache_size=100,
            )

        # create async connection pool with wrapped creator
        return create_async_engine(
            'postgresql+asyncpg://',
            creator=adapted_creator,
            # Use NullPool to disable connection pooling and avoid event loop issues
            poolclass=NullPool,
        )
    else:
        host_string = (
            f'postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
        )
        return create_async_engine(
            host_string,
            # Use NullPool to disable connection pooling and avoid event loop issues
            poolclass=NullPool,
        )


engine = _get_db_engine()
session_maker = sessionmaker(bind=engine)

a_engine = _get_async_db_engine()
a_session_maker = sessionmaker(
    bind=a_engine,
    class_=AsyncSession,
    expire_on_commit=False,
    # Configure the session to use the same connection for all operations in a transaction
    # This helps prevent the "Task got Future attached to a different loop" error
    future=True,
)
