"""
Utility functions.
"""

import logging
import os
from functools import lru_cache
from pathlib import Path
from typing import Iterator, List, Optional

from dotenv import load_dotenv
from rich.logging import RichHandler
from sqlalchemy.engine import Engine
from sqlmodel import Session, SQLModel, create_engine
from yarl import URL

from datajunction.config import Settings
from datajunction.typing import ColumnType


def setup_logging(loglevel: str) -> None:
    """
    Setup basic logging.
    """
    level = getattr(logging, loglevel.upper(), None)
    if not isinstance(level, int):
        raise ValueError(f"Invalid log level: {loglevel}")

    logformat = "[%(asctime)s] %(levelname)s: %(name)s: %(message)s"
    logging.basicConfig(
        level=level,
        format=logformat,
        datefmt="[%X]",
        handlers=[RichHandler(rich_tracebacks=True)],
        force=True,
    )


def get_project_repository() -> Path:
    """
    Return the project repository.

    This is used for unit tests.
    """
    return Path(__file__).parent.parent.parent


@lru_cache
def get_settings() -> Settings:
    """
    Return a cached settings object.
    """
    dotenv_file = os.environ.get("DOTENV_FILE", ".env")
    load_dotenv(dotenv_file)
    return Settings()


def get_engine() -> Engine:
    """
    Create the metadata engine.
    """
    settings = get_settings()
    engine = create_engine(settings.index)

    return engine


def create_db_and_tables() -> None:
    """
    Create the database and tables.
    """
    engine = get_engine()
    SQLModel.metadata.create_all(engine)


def get_session() -> Iterator[Session]:
    """
    Per-request session.
    """
    engine = get_engine()

    with Session(engine, autoflush=False) as session:  # pragma: no cover
        yield session


def get_name_from_path(repository: Path, path: Path) -> str:
    """
    Compute the name of a node given its path and the repository path.
    """
    # strip anything before the repository
    relative_path = path.relative_to(repository)

    if len(relative_path.parts) < 2 or relative_path.parts[0] not in {
        "nodes",
        "databases",
    }:
        raise Exception(f"Invalid path: {path}")

    # remove the "nodes" directory from the path
    relative_path = relative_path.relative_to(relative_path.parts[0])

    # remove extension
    relative_path = relative_path.with_suffix("")

    # encode percent symbols and periods
    encoded = (
        str(relative_path)
        .replace("%", "%25")
        .replace(".", "%2E")
        .replace(os.path.sep, ".")
    )

    return encoded


def get_more_specific_type(
    current_type: Optional[ColumnType],
    new_type: ColumnType,
) -> ColumnType:
    """
    Given two types, return the most specific one.

    Different databases might store the same column as different types. For example, Hive
    might store timestamps as strings, while Postgres would store the same data as a
    datetime.

        >>> get_more_specific_type(ColumnType.STR, ColumnType.DATETIME)
        <ColumnType.DATETIME: 'DATETIME'>
        >>> get_more_specific_type(ColumnType.STR, ColumnType.INT)
        <ColumnType.INT: 'INT'>

    """
    if current_type is None:
        return new_type

    hierarchy = [
        ColumnType.BYTES,
        ColumnType.STR,
        ColumnType.FLOAT,
        ColumnType.INT,
        ColumnType.DECIMAL,
        ColumnType.BOOL,
        ColumnType.DATETIME,
        ColumnType.DATE,
        ColumnType.TIME,
        ColumnType.TIMEDELTA,
        ColumnType.LIST,
        ColumnType.DICT,
    ]

    return sorted([current_type, new_type], key=hierarchy.index)[1]


def get_issue_url(
    baseurl: URL = URL("https://github.com/DataJunction/datajunction/issues/new"),
    title: Optional[str] = None,
    body: Optional[str] = None,
    labels: Optional[List[str]] = None,
) -> URL:
    """
    Return the URL to file an issue on GitHub.

    https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue#creating-an-issue-from-a-url-query
    """
    query_arguments = {
        "title": title,
        "body": body,
        "labels": ",".join(label.strip() for label in labels) if labels else None,
    }
    query_arguments = {k: v for k, v in query_arguments.items() if v is not None}

    return baseurl % query_arguments

datajunction.config.Settings sqlmodel.create_engine sqlmodel.Session sqlmodel.SQLModel.metadata.create_all os.environ.get dotenv.load_dotenv pathlib.Path rich.logging.RichHandler yarl.URL
[((1186, 1223), 'os.environ.get', 'os.environ.get', (['"""DOTENV_FILE"""', '""".env"""'], {}), "('DOTENV_FILE', '.env')\n", (1200, 1223), False, 'import os\n'), ((1228, 1252), 'dotenv.load_dotenv', 'load_dotenv', (['dotenv_file'], {}), '(dotenv_file)\n', (1239, 1252), False, 'from dotenv import load_dotenv\n'), ((1264, 1274), 'datajunction.config.Settings', 'Settings', ([], {}), '()\n', (1272, 1274), False, 'from datajunction.config import Settings\n'), ((1396, 1425), 'sqlmodel.create_engine', 'create_engine', (['settings.index'], {}), '(settings.index)\n', (1409, 1425), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((1565, 1601), 'sqlmodel.SQLModel.metadata.create_all', 'SQLModel.metadata.create_all', (['engine'], {}), '(engine)\n', (1593, 1601), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((3698, 3760), 'yarl.URL', 'URL', (['"""https://github.com/DataJunction/datajunction/issues/new"""'], {}), "('https://github.com/DataJunction/datajunction/issues/new')\n", (3701, 3760), False, 'from yarl import URL\n'), ((1721, 1753), 'sqlmodel.Session', 'Session', (['engine'], {'autoflush': '(False)'}), '(engine, autoflush=False)\n', (1728, 1753), False, 'from sqlmodel import Session, SQLModel, create_engine\n'), ((836, 869), 'rich.logging.RichHandler', 'RichHandler', ([], {'rich_tracebacks': '(True)'}), '(rich_tracebacks=True)\n', (847, 869), False, 'from rich.logging import RichHandler\n'), ((1034, 1048), 'pathlib.Path', 'Path', (['__file__'], {}), '(__file__)\n', (1038, 1048), False, 'from pathlib import Path\n')]