import logging

import boto3  # type: ignore

from ...utils import SerializedAsset
from .credentials import GlueAthenaCredentials  # type: ignore

logger = logging.getLogger(__name__)

_S3_PREFIX = "s3://"
_S3_CONSOLE_BUCKETS_URL = "https://s3.console.aws.amazon.com/s3/buckets/"
_GLUE_DATABASE_NAME = "awsdatacatalog"  # https://docs.aws.amazon.com/redshift/latest/mgmt/query-editor-v2-glue.html


def _s3_to_http(url: str) -> str:
    """Convert s3:// links to the AWS console URL."""
    if not url.startswith(_S3_PREFIX):
        return url

    s3_path = url.removeprefix(_S3_PREFIX)
    parts = s3_path.split("/", 1)
    bucket = parts[0]
    object_name = parts[1] if len(parts) > 1 else ""
    return f"{_S3_CONSOLE_BUCKETS_URL}{bucket}/?prefix={object_name}"


def _format_table_description(
    location: str,
    description: str | None,
) -> str | None:
    """Append bucket location details to a table description."""

    if not location:
        return description or None

    url = _s3_to_http(location)
    location_md = f"Bucket location: [{location}]({url})"
    if not description:
        return location_md

    return f"{description}\n{location_md}"


def _table_type(table_type: str | None) -> str:
    """Normalize Glue table types into catalog-friendly labels."""
    if not table_type:
        return "TABLE"
    if table_type.upper() == "VIRTUAL_VIEW":
        return "VIEW"
    if table_type.upper() == "EXTERNAL_TABLE":
        return "EXTERNAL"
    return "TABLE"


def _parse_column(
    table_id: str,
    raw_column: dict,
    ordinal_position: int,
) -> dict | None:
    """Convert a Glue column entry into a catalog column payload."""
    column_name = raw_column.get("Name")
    if not column_name:
        return None
    return {
        "column_id": f"{table_id}.{column_name}",
        "column_name": column_name,
        # Keep Glue raw type; mapping to Catalog ColumnDataType is done at store.
        "data_type": raw_column["Type"],
        "description": raw_column.get("Comment"),
        "ordinal_position": ordinal_position,
        "table_id": table_id,
        "tags": [],
    }


def _parse_table(
    raw_table: dict,
    schema_id: str,
    table_arn: str,
) -> dict:
    table_name = raw_table["Name"]
    description = raw_table.get("Description")
    storage = raw_table.get("StorageDescriptor", {})
    location = storage["Location"]
    table_type = _table_type(raw_table.get("TableType"))
    return {
        "description": _format_table_description(location, description),
        "schema_id": schema_id,
        "table_id": table_arn,
        "table_name": table_name,
        "tags": [],
        "type": table_type,
    }


def _iter_table_columns(raw_table: dict, table_arn: str) -> SerializedAsset:
    """
    Build one ordered column list from storage columns and partition keys.

    Duplicate names are skipped so each logical column appears once.
    """
    columns: SerializedAsset = []
    ordinal_position = 0
    seen_columns: set[str] = set()
    column_entries = (
        ("column", raw_table.get("StorageDescriptor", {}).get("Columns", [])),
        ("partition key", raw_table.get("PartitionKeys", [])),
    )

    for column_kind, raw_columns in column_entries:
        for raw_column in raw_columns:
            column_name = raw_column.get("Name")
            if not column_name:
                continue
            if column_name in seen_columns:
                continue

            parsed = _parse_column(table_arn, raw_column, ordinal_position)
            if parsed is None:
                continue
            columns.append(parsed)
            seen_columns.add(column_name)
            ordinal_position += 1

    return columns


class GlueAthenaClient:
    """Fetch Glue catalog metadata for Glue/Athena."""

    def __init__(
        self,
        credentials: GlueAthenaCredentials,
    ) -> None:
        self._region = credentials.aws_region
        self._account_id = credentials.aws_account_id

        session = boto3.session.Session(
            region_name=credentials.aws_region,
            aws_access_key_id=credentials.access_key_id,
            aws_secret_access_key=credentials.access_key_secret,
        )
        self._glue_client = session.client("glue")

    def databases(self) -> SerializedAsset:
        """
        Return the single AWS Data Catalog database asset.

        AWS exposes Glue through the built-in `awsdatacatalog` database:
        https://docs.aws.amazon.com/redshift/latest/mgmt/query-editor-v2-glue.html
        """
        return [
            {
                "database_id": _GLUE_DATABASE_NAME,
                "database_name": _GLUE_DATABASE_NAME,
            }
        ]

    @property
    def _glue_arn_prefix(self) -> str:
        return f"arn:aws:glue:{self._region}:{self._account_id}"

    def _resource_arn(self, resource: str, identifier: str) -> str:
        return f"{self._glue_arn_prefix}:{resource}/{identifier}"

    def schemas(self) -> SerializedAsset:
        """
        Return schema assets by calling Glue `get_databases()`.

        Naming is intentionally normalized to schemas here, even though the
        Glue endpoint is named "databases".
        """
        schemas: SerializedAsset = []
        paginator = self._glue_client.get_paginator("get_databases")
        for page in paginator.paginate():
            # Glue's `get_databases` endpoint returns logical schemas.
            for raw_schema in page.get("DatabaseList", []):
                schema_name = raw_schema["Name"]
                schema_arn = self._resource_arn("database", schema_name)
                schemas.append(
                    {
                        "database_id": _GLUE_DATABASE_NAME,
                        "schema_id": f"{_GLUE_DATABASE_NAME}.{schema_arn}",
                        "schema_name": schema_name,
                        "description": raw_schema.get("Description"),
                        "tags": [],
                    }
                )
        return schemas

    def tables_and_columns(
        self,
        schemas: SerializedAsset,
    ) -> tuple[SerializedAsset, SerializedAsset]:
        """Collect tables and columns for each schema."""
        tables: SerializedAsset = []
        columns: SerializedAsset = []

        paginator = self._glue_client.get_paginator("get_tables")
        for schema in schemas:
            schema_name = schema["schema_name"]
            schema_id = schema["schema_id"]

            page_iterator = paginator.paginate(DatabaseName=schema_name)
            for page in page_iterator:
                for raw_table in page.get("TableList", []):
                    table_arn = self._resource_arn(
                        "table",
                        f"{schema_name}/{raw_table['Name']}",
                    )
                    table = _parse_table(raw_table, schema_id, table_arn)
                    tables.append(table)

                    table_columns = _iter_table_columns(raw_table, table_arn)
                    columns.extend(table_columns)

        return tables, columns
