"""Shared adapter behavior and temporary connection testing."""

import os
from pathlib import Path

from sqlalchemy import URL, create_engine, text
from sqlalchemy.pool import NullPool

from app.core.data_source.errors import (
    DataSourceConfigurationInvalid,
    DataSourceConnectionFailed,
)


READ_ONLY_PURPOSES = {
    "metadata_collection",
    "metadata_preview",
    "quality_check",
    "dataflow_read",
}
PURPOSES = READ_ONLY_PURPOSES | {"connection_test", "dataflow_write"}


class BaseDataSourceAdapter:
    database_type = ""
    drivername = ""
    allowed_tls_options = frozenset()
    certificate_options = frozenset()

    def __init__(self, certificate_dir=None):
        self.certificate_dir = Path(
            certificate_dir
            or os.environ.get(
                "DATASOURCE_CERT_DIR",
                "/etc/dataops-platform/datasource-certs",
            )
        ).resolve()

    def validate_options(self, options):
        options = dict(options or {})
        unsupported = sorted(set(options) - set(self.allowed_tls_options))
        if unsupported:
            raise DataSourceConfigurationInvalid(
                "unsupported TLS option"
            )
        normalized = {}
        for key, value in options.items():
            if key in self.certificate_options:
                path = Path(str(value)).expanduser().resolve()
                try:
                    path.relative_to(self.certificate_dir)
                except ValueError as exc:
                    raise DataSourceConfigurationInvalid(
                        "certificate path is outside the configured directory"
                    ) from exc
                if not path.is_file():
                    raise DataSourceConfigurationInvalid(
                        "certificate file does not exist"
                    )
                normalized[key] = str(path)
            else:
                normalized[key] = self._validate_scalar_option(key, value)
        return normalized

    def _validate_scalar_option(self, key, value):
        return str(value)

    def validate_definition(self, definition):
        if definition.database_type not in {
            self.database_type,
            "postgres" if self.database_type == "postgresql" else self.database_type,
        }:
            raise DataSourceConfigurationInvalid(
                "data source type does not match adapter"
            )
        if not str(definition.host).strip():
            raise DataSourceConfigurationInvalid("host is required")
        if definition.port < 1 or definition.port > 65535:
            raise DataSourceConfigurationInvalid("port is invalid")
        if not str(definition.database).strip():
            raise DataSourceConfigurationInvalid("database is required")
        self.validate_options(definition.tls_options)

    def build_url(self, definition, credential):
        self.validate_definition(definition)
        return URL.create(
            drivername=self.drivername,
            username=credential.username,
            password=credential.password,
            host=definition.host,
            port=definition.port,
            database=definition.database,
        )

    def connect_args(self, definition, credential, query_timeout):
        raise NotImplementedError

    def probe(self, connection):
        connection.execute(text("SELECT 1"))

    def configure_transaction(self, connection, purpose):
        if purpose not in PURPOSES:
            raise DataSourceConfigurationInvalid(
                "unsupported data source purpose"
            )
        if purpose in READ_ONLY_PURPOSES:
            connection.execute(text("SET TRANSACTION READ ONLY"))

    def test_connection(
        self,
        definition,
        credential,
        *,
        query_timeout,
    ):
        engine = create_engine(
            self.build_url(definition, credential),
            poolclass=NullPool,
            connect_args=self.connect_args(
                definition,
                credential,
                query_timeout=query_timeout,
            ),
        )
        try:
            with engine.connect() as connection:
                self.probe(connection)
        except Exception as exc:
            raise DataSourceConnectionFailed() from exc
        finally:
            engine.dispose()

    def create_pooled_engine(self, definition, credential, settings):
        return create_engine(
            self.build_url(definition, credential),
            pool_size=int(settings["pool_size"]),
            max_overflow=int(settings["max_overflow"]),
            pool_timeout=int(settings["pool_timeout"]),
            pool_recycle=int(settings["pool_recycle"]),
            pool_pre_ping=True,
            pool_use_lifo=True,
            pool_reset_on_return="rollback",
            connect_args=self.connect_args(
                definition,
                credential,
                query_timeout=int(settings["query_timeout"]),
            ),
        )
