# pool.py
#
# Copyright Uberware. All Rights Reserved

"""Manage Pools."""

import logging
import typing
from uuid import UUID

from .component import abort, run_component, run_simple_command
from .config import SMEDGE

# Use Python standard logging
logger = logging.getLogger("smedge")


POOL = SMEDGE / "PoolManager"
"""Location of the PoolManager executable, uses config.SMEDGE by default"""


class Pool(object):
    """Wraps the pool ID and name

    Converting to a string will convert this to only the ID.
    Use the repr format to see both the ID and name as a string.
    It weill compare equality with a string, a UUID, or another Pool object.
    You don't have to use this to submit jobs, but can."""

    def __init__(self, pool_id: UUID, pool_name: str):
        self.id = pool_id
        self.name = pool_name

    def __str__(self):
        return str(self.id)

    def __repr__(self):
        return f"Pool({self.id}: {self.name})"

    def __eq__(self, other):
        if isinstance(other, Pool):
            return self.id == other.id
        elif isinstance(other, UUID):
            return self.id == other
        elif isinstance(other, str):
            try:
                return self.id == UUID(other)
            except ValueError:
                return self.name == other
        else:
            return False


AnyPoolType = typing.Union[str, UUID, Pool]
"""Type used for any method that can accept pools as input"""


def get_pools() -> typing.List[Pool]:
    """List all pools

    Returns:
        a list of tuples of the Pool ID and Name

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [POOL, "List"]
    result = run_component(cmd)
    pools = []
    for line in result.stdout.split("\n"):
        if line and line != "No Pools Available.":
            pool = _check_for_pool(line)
            if pool is None:
                abort(result, f"Invalid pool: {line}")
            pools.append(pool)
    return pools


def create_pools(names: typing.Union[str, typing.List[str]]) -> typing.List[Pool]:
    """Create pools

    Arguments:
        names: one or more names of pools to create

    Returns:
        A list of Pool objects for each pool created

    Raises:
        SmedgeComponentError: Something went wrong
    """
    result = run_simple_command(POOL, "Create", names, "pools")
    pools = []
    for line in result.stdout.split("\n"):
        pool = _check_for_pool(line)
        if pool is not None:
            if pool.name not in names:
                abort(result, f"Received unexpected pool: {pool}")
            names.remove(pool.name)
            pools.append(pool)
    if names:
        abort(result, f"Did not receive pool IDs for: {names}")
    return pools


def rename_pool(pool: AnyPoolType, new_name: str) -> Pool:
    """Rename a pool

    Arguments:
        pool: the pool to rename. Supply a name, an ID, or a Pool object
        new_name: the new name for the pool

    Returns:
        A Pool object with the new pool name

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [POOL, "Rename", str(pool), new_name]
    result = run_component(cmd)
    for line in result.stdout.split("\n"):
        if "Cannot find a pool to rename" in line:
            abort(result, f"Could not find pool {pool}")
        if "Renaming pool" in line:
            continue
        new_pool = _check_for_pool(line)
        if new_pool:
            logger.info(f"Renamed pool {pool}: {new_pool!r}")
            return new_pool
    abort(result, "Failed to rename pool")


def delete_pools(pools: typing.Union[AnyPoolType, typing.List[AnyPoolType]]):
    """Delete pools

    Arguments:
        pools: one or more pools to delete. Supply names, IDs, or Pool objects.

    Raises:
        SmedgeComponentError: Something went wrong
    """
    result = run_simple_command(POOL, "Delete", pools, "pools")
    for line in result.stdout.split("\n"):
        if not line or "Deleting" in line:
            continue
        pool = _check_for_pool(line)
        if not pool:
            abort(result, f"Got invalid pool line: {line}")
        pools = [x for x in pools if pool != x]
        logger.info(f"Removed pool: {pool!r}")
    if pools:
        abort(result, f"Had leftover pools: {pools}")


def _check_for_pool(line: str) -> typing.Optional[Pool]:
    """Returns a tuple (id, name) if found, otherwise None"""
    if line and len(line) > 36:
        try:
            pool_id = UUID(line[:36])
            pool_name = line[37:]
            return Pool(pool_id, pool_name)
        except ValueError:
            pass
    return None
