from __future__ import annotations

import logging
import os
import re
import secrets
import string
from typing import List, Optional

import pandas as pd

from db.ports import IngestRepository
from db.schemas import ImageShortUrlCreate, ImageShortUrlRead

logger = logging.getLogger(__name__)

_SLUG_ALPHABET = string.ascii_letters + string.digits
_SLUG_LENGTH = 12
_MAX_RETRIES = 10

# Columns in the normalized Magento DataFrame that may contain image URLs.
_IMAGE_COLUMNS: List[str] = ["base_image", "small_image", "thumbnail_image", "additional_images"]

# Plytix can send multiple URLs comma-separated; we also support pipe. Split on either to get individual URLs.
_IMAGE_URL_SPLIT_RE = re.compile(r"[,|]+")
# Output: comma-separated shortened URLs (Magento-friendly).
_IMAGE_URL_OUTPUT_SEPARATOR = ","


def _generate_slug() -> str:
    return "".join(secrets.choice(_SLUG_ALPHABET) for _ in range(_SLUG_LENGTH))


def shorten_image_url(repo: IngestRepository, destination_url: str) -> ImageShortUrlRead:
    """Return an existing short URL record for *destination_url* or create a new one.

    Idempotent: calling this twice with the same destination returns the same slug.
    Raises RuntimeError if a unique slug cannot be generated after _MAX_RETRIES attempts.
    """
    existing = repo.get_short_url_by_destination(destination_url)
    if existing:
        return existing

    for _ in range(_MAX_RETRIES):
        slug = _generate_slug()
        if repo.get_short_url_by_slug(slug) is None:
            return repo.create_short_url(ImageShortUrlCreate(slug=slug, destination_url=destination_url))

    raise RuntimeError("Could not generate a unique slug after multiple attempts")


def resolve_slug(repo: IngestRepository, slug: str) -> Optional[ImageShortUrlRead]:
    """Look up a slug and return the record, or None if not found."""
    return repo.get_short_url_by_slug(slug)


def _short_url_base(base_url: Optional[str] = None) -> str:
    return (base_url or os.getenv("BASE_IMAGE_URL", "")).rstrip("/")


def is_short_image_url(url: str, *, base_url: Optional[str] = None) -> bool:
    """True when *url* is a PlytixMage short redirect (BASE_IMAGE_URL + slug)."""
    text = str(url or "").strip()
    resolved_base = _short_url_base(base_url)
    if not text or not resolved_base or not text.startswith(resolved_base):
        return False
    slug = text.rstrip("/").split("/")[-1]
    return bool(slug) and slug != "api" and slug != "i"


def resolve_destination_image_url(
    url: str,
    *,
    repo: Optional[IngestRepository] = None,
    base_url: Optional[str] = None,
) -> str:
    """Resolve PlytixMage short image URLs to their external destination before channel push."""
    text = str(url or "").strip()
    if not text or not is_short_image_url(text, base_url=base_url):
        return text
    if repo is None:
        return text
    slug = text.rstrip("/").split("/")[-1]
    record = resolve_slug(repo, slug)
    if record and record.destination_url:
        return str(record.destination_url).strip()
    return text


def resolve_destination_image_urls(
    urls: List[str],
    *,
    repo: Optional[IngestRepository] = None,
    base_url: Optional[str] = None,
) -> List[str]:
    """Resolve and dedupe image URLs for external upload (Shopify productCreateMedia, etc.)."""
    seen: set[str] = set()
    resolved: List[str] = []
    for url in urls:
        dest = resolve_destination_image_url(url, repo=repo, base_url=base_url)
        if dest and dest not in seen:
            seen.add(dest)
            resolved.append(dest)
    return resolved


def _needs_shortening(url: str, limit: int) -> bool:
    if not url:
        return False
    return limit == 0 or len(url) > limit


def _shorten_single(repo: IngestRepository, base_url: str, url: str, limit: int) -> str:
    """Shorten *url* if it exceeds *limit*, otherwise return it unchanged."""
    url = url.strip()
    if not url or not _needs_shortening(url, limit):
        logger.info("_shorten_single: skipping empty/below-limit url=%r", url)
        return url
    try:
        record = shorten_image_url(repo, url)
        short = f"{base_url}/{record.slug}"
        logger.info("_shorten_single: %r -> %r (slug=%s)", url, short, record.slug)
        return short
    except Exception:
        logger.exception("_shorten_single: failed for url=%r", url)
        return url


def _parse_image_urls(cell: object) -> List[str]:
    """Split a cell value into a list of URLs. Handles Plytix comma-separated and pipe-separated."""
    text = str(cell).strip() if cell is not None else ""
    if not text or text.lower() in {"nan", "none", "null", ""}:
        return []
    parts = [p.strip() for p in _IMAGE_URL_SPLIT_RE.split(text) if p.strip()]
    return parts


def shorten_image_urls_in_df(
    df: pd.DataFrame,
    repo: IngestRepository,
    *,
    base_url: Optional[str] = None,
    url_length_limit: int = 0,
) -> pd.DataFrame:
    """Shorten image URLs in *df* that exceed *url_length_limit* characters.

    A limit of 0 (the default) shortens every non-empty image URL unconditionally,
    which is the recommended setting to avoid Magento's 240-character thumbnail limit.

    Operates on :data:`_IMAGE_COLUMNS` that are present in *df*.
    Input: Plytix may send multiple URLs comma- or pipe-separated in a single cell;
    we split on comma and pipe, shorten each URL separately, and output comma-separated.

    If *base_url* is not provided it is read from the ``BASE_IMAGE_URL`` env var.
    Returns *df* unchanged (no copy) when the feature is not configured.
    """
    resolved_base = (base_url or os.getenv("BASE_IMAGE_URL", "")).rstrip("/")
    logger.info("shorten_image_urls_in_df: entered, resolved_base=%r, rows=%d, cols=%s",
                resolved_base, len(df), list(df.columns))
    if not resolved_base:
        logger.info("shorten_image_urls_in_df: skipping — BASE_IMAGE_URL not set")
        return df

    df = df.copy()

    for col in _IMAGE_COLUMNS:
        if col not in df.columns:
            logger.info("shorten_image_urls_in_df: column %r not in df, skipping", col)
            continue
        logger.info("shorten_image_urls_in_df: processing column %r", col)

        def _shorten_cell(cell: object, _base: str = resolved_base, _limit: int = url_length_limit) -> str:
            urls = _parse_image_urls(cell)
            if not urls:
                return ""
            shortened = [_shorten_single(repo, _base, u, _limit) for u in urls]
            return _IMAGE_URL_OUTPUT_SEPARATOR.join(shortened)

        df[col] = df[col].apply(_shorten_cell)

    return df
