import datetime
import urllib.parse
from collections import OrderedDict


def clean_name(name: str | None) -> str | None:
    if name is None:
        return None
    if name == "":
        return None

    # remove inc, ltd, etc
    suffixes = [
        "Inc",
        "Ltd",
        "Corporate",
        "Technologies",
        "Trading",
        "Trading",
        "Foundation",
        "Labs",
        "Solutions",
        "Corporation",
        "Pte",
        "Co",
        "LLC",
        "Incorporated",
        "International",
        "Computer",
        "Systems",
        "Limited",
        "Smart",
        "(Trading)",
        "Networks",
    ]
    # Create a case-insensitive version of suffixes list
    suffixes_lower = [suffix.lower() for suffix in suffixes]

    # Remove periods and commas
    name = name.replace(".", " ").replace(",", " ")

    # Split the name into words
    words = name.split()

    # Filter out the suffixes (case-insensitive)
    filtered_words = [word for word in words if word.lower() not in suffixes_lower]

    # Join the remaining words back together
    cleaned_name = " ".join(filtered_words)

    return cleaned_name.rstrip()


def common_substr(strings: list[str]) -> str | None:
    if not strings:
        return None
    strings_set = set(filter(None, strings))
    if len(strings_set) < 1:
        return None

    # logic to get shortest common substring
    # disabled for now
    # failure case: Lutron SmartBridge and Lutron Smart Bridge gets shown as "Smart"
    # shortest = min(strings, key=len)
    # common = ""

    # for i, char in enumerate(shortest):
    #     if all(s[i] == char for s in strings):
    #         common += char
    #     else:
    #         break

    # if len(common.strip()) < 4:
    return " / ".join(strings_set)

    # return common.strip()


def natural_join(
    items_all: list[str | None],
    conjunction: str = "and",
    singular_name: str = "other",
    empty_name: str | None = None,
    dedup: bool = False,
    count_noun: str = "items",
) -> str:
    """
    Join a list of items with commas and a conjunction before the last item.
    Uses a dict to ensure uniqueness when dedup=True, otherwise preserves order and counts

    Args:
        items_all: List of strings to join
        conjunction: The word to use before the last item (default: 'and')
        singular_name: The name to use for None values (default: 'other')
        empty_name: The name to use when the list is empty (default: 'None')
        dedup: Whether to deduplicate items (default: True)
        count_noun: The noun to use when counting duplicate items (default: 'devices')

    Returns:
        A string with items joined in a natural language format
    """
    none_count = 0

    if dedup:
        # Original behavior: use dict to deduplicate
        items: dict[str, None] = {}
        for item in items_all:
            if item is None:
                none_count += 1
            elif item != "":
                items[item] = None
        items_list = list(items.keys())
    else:
        # New behavior: preserve order and count occurrences
        item_counts = OrderedDict()
        for item in items_all:
            if item is None:
                none_count += 1
            elif item != "":
                item_counts[item] = item_counts.get(item, 0) + 1

        items_list = []
        for item, count in item_counts.items():
            if count > 1:
                items_list.append(f"{item} ({count} {count_noun})")
            else:
                items_list.append(item)

    if len(items_list) == 0:
        if empty_name:
            return empty_name
        elif none_count == 0:
            return f"No {count_noun}"
        elif none_count == 1:
            return f"1 {singular_name}"
        else:
            return f"{none_count} {count_noun}"

    if none_count == 1:
        items_list.append("1 other")
    elif none_count > 1:
        items_list.append(f"{none_count} others")

    if len(items_list) == 1:
        return items_list[0]

    if len(items_list) == 2:
        return f"{items_list[0]} {conjunction} {items_list[1]}"

    return f"{', '.join(items_list[:-1])}, {conjunction} {items_list[-1]}"


def natural_join_categories(categories: list[str] | str | None) -> str:
    if categories is None:
        return "Unknown"

    if isinstance(categories, str):
        categories = [categories]

    categories_new = []
    for category in categories:
        # replace _ with space and title case
        if category == "audio_video_player":
            categories_new.append("Media")
        elif category == "site_analytics":
            categories_new.append("Analytics")
        elif isinstance(category, str):
            categories_new.append(category.replace("_", " ").title())

    if len(categories_new) == 0:
        return "Unknown"

    return ", ".join(categories_new)


def format_mb(size: int) -> str:
    """
    Format a size in bytes to a human-readable string in MB.

    Args:
        size: Size in bytes

    Returns:
        Formatted size in MB with two decimal places
    """
    if size == 0:
        return "—"
    mb_size = size / (1024 * 1024)
    if mb_size < 1:
        return "<1 MB"

    return f"{mb_size:.0f} MB"


def format_text_fragments(excerpts: list[str], url: str):
    return (
        url
        + "#:~:"
        + "&".join(
            [
                "text=" + urllib.parse.quote(excerpt, safe="")
                for excerpt in reversed(excerpts)
            ]
        )
    )


def format_datetime(d: datetime.datetime) -> str:
    diff = datetime.datetime.now() - d
    s = diff.seconds
    # note that diff.days checks should go first, the timedelta is days + seconds
    if diff.days > 7 or diff.days < 0:
        return d.strftime("%d %b %y")
    elif diff.days == 1:
        return "1 day ago"
    elif diff.days > 1:
        return "{} days ago".format(diff.days)
    elif s < 3600:
        return "in the last hour"
    elif s < 7200:
        return "in the last 2 hours"
    else:
        return "{} hours ago".format(s // 3600)


def format_datetime_network_table(d: datetime.datetime) -> str:
    diff = datetime.datetime.now() - d
    s = diff.seconds
    # note that diff.days checks should go first, the timedelta is days + seconds
    if diff.days > 7 or diff.days < 0:
        return d.strftime("%d %b %y")
    elif diff.days == 1:
        return "1 day ago"
    elif diff.days > 1:
        return "{} days ago".format(diff.days)
    elif s < 720:
        return "just now"
    elif s < 3600:
        return "{} minutes ago".format(s // 60)
    elif s < 7200:
        return "1 hour ago"
    else:
        return "{} hours ago".format(s // 3600)
