import logging
from django.conf import settings
from django.utils import timezone
from django.contrib.auth.hashers import make_password
from purist.models import AccountType
from middleware.common import get_woo_connection
from purist.limitmonitor import ServicesContainer
from limitmonitor.models import ExternalBundle, Limit
from limitmonitor.tunnel import TunnelManager
from limitmonitor.subscription import SubscriptionManager, SubscriptionStatus
from invitation.models import Invitation


logger = logging.getLogger(__name__)


class OrderParseError(Exception):
    """Raised when the order being parsed contains not expected data.

    """
    pass


def get_username_from_woo_customer_id(customer_id, woo=None):
    if woo is None:
        woo = get_woo_connection()

    try:
        query = "customers/" + str(customer_id)
        result = woo.get(query).json()
        return result["username"]
    except Exception:
        logger.exception(
            "Could not retrieve username for customer_id " + str(customer_id))
        return "invalid"


def get_services_from_bundle(bundle_key):
    ext_buns = ExternalBundle.objects.filter(external_key=bundle_key)
    result = []
    for ext_bun in ext_buns:
        result.append(ext_bun.service)
    return result


def update_services(user, srv_to_disable, srv_to_enable, renewal_date):
    # Disable services
    for srvn in srv_to_disable:
        serv = user.limit_set.get(service=srvn)
        serv.disable()
        serv.save()

    # Enable services
    for srvn in srv_to_enable:
        serv = user.limit_set.get(service=srvn)
        serv.enable(renewal_date)
        serv.save()


def forced_update(user):
    subscriptions = SubscriptionManager.search_subscriptions(user)
    if len(subscriptions) == 0:
        logger.info('User {} has no subscriptions.'.format(user.username))
        return

    for sub in subscriptions:
        if sub.variation_id not in settings.WOOSUB1_PRODUCT_LIST:
            logger.info('Subscription {} for user {} does not belong '
                        'to {}'.format(sub.subscription_id,
                                       user.username,
                                       settings.SITE_TITLE))
            continue  # Skip this subscription as is not a product we care of

        bndl_srvs = set(get_services_from_bundle(sub.variation_id))
        cur_srvs = set(user.get_enabled_limits())

        if sub.status == SubscriptionStatus.ACTIVE:
            srv_to_enable = bndl_srvs - cur_srvs
            srv_to_disable = cur_srvs - bndl_srvs
            srv_to_reenable = bndl_srvs & cur_srvs

            try:
                update_services(user, srv_to_disable,
                                srv_to_enable | srv_to_reenable,
                                sub.next_payment_date)
                user.account_type = sub.account_type
                user.save()
                if hasattr(user, 'chosenreward'):
                    user.chosenreward.is_pending = False
                    user.chosenreward.save()
            except TunnelManager.TerminateAccountError as e1:
                error = '{}: {}'.format(sub.subscription_id, repr(e1))
                logger.error(error)
            except Exception as e2:
                error = '{}: {}'.format(sub.subscription_id, repr(e2))
                logger.error(error)

        # TODO: process other subscription statuses
        else:
            logger.info('Subscription {} for user {} status is: {}'.format(
                sub.subscription_id, user.username, sub.status.value))


def parse_order(order):
    if order is None:
        raise OrderParseError('Cannot parse None order.')

    pending_status = ['pending', 'on-hold']
    status = order.get('status', None)

    parsed_order = {}
    if status:
        parsed_order['status'] = status
        parsed_order['is_pending'] = status in pending_status
    else:
        raise OrderParseError('Error: order has no status.')
    return parsed_order


def delete_account(user, purge_n=0, purge=False):
    """When called with purge=False it will remove user from all LDAP
    groups, terminate tunnel account, remove PII from WooCommerce
    account, mark the user as inactive, set a gibberish password for
    LDAP and WooCommerce and set purge value to purge_n.

    When called with purge=True it will fully delete the user from
    LDAP and from Django DB.

    """
    # Get lowercase names of LDAP groups available from services
    ldap_groups = [s.lower() for i, s in ServicesContainer.choices()]
    # Remove undefined as there is no LDAP group with that name
    ldap_groups.remove('undefined')

    # Remove user from all LDAP groups
    for ldap_group in ldap_groups:
        user.remove_ldap_group(ldap_group)

    # Terminate tunnel account
    if user.has_tunnel_account():
        try:
            user.terminate_tunnel_account()
        except TunnelManager.TerminateAccountError as e:
            logger.error(repr(e))

    # Set unusable password for WooCommerce
    user.set_woocommerce_password(make_password(None))

    # Set unusable password for LDAP
    user.set_ldap_password(make_password(None))

    # Clear billing and recovery emails
    user.email = ''
    user.billing_email = ''

    # Mark user as deleted in Django
    user.is_active = False

    # Set purge value
    user.purge = purge_n
    user.save()

    # Cancell all user subscriptions
    subs = SubscriptionManager.search_subscriptions(user)
    for sub in subs:
        sub.status = SubscriptionStatus.CANCELLED
        sub.save()

    # Remove WC PII
    user.remove_woocommerce_pii()

    # If the account is of type invitee free up that invitation slot
    if user.account_type == AccountType.INVITED:
        try:
            inv = Invitation.objects.get(guest=user)
            inv.delete()
        except Invitation.DoesNotExist as e:
            logger.error(repr(e))

    if purge:
        ldap_user = user.get_ldap()
        ldap_user.delete()
        user.delete_woocommerce_account()
        user.delete()


def get_users_expired_subs(date_limit=None):
    """Returns the list of users for which the service renewal_date is
    older than date_limit. If date_limit is not specified
    timezone.now() will be used. It will omit inactive users and users
    created with magic carts.

    """
    magic_carts = [0, 999, 5000, 5999]
    if date_limit is None:
        limit = timezone.now()
    else:
        limit = date_limit

    limits = Limit.objects.filter(renewal_date__lte=limit)

    users = []
    for limit in limits:
        user = limit.user

        if hasattr(user, 'chosenreward'):
            is_magic = user.chosenreward.reward in magic_carts
        else:
            is_magic = False

        if (
            user not in users and user.is_active
            and not is_magic
        ):
            users.append(user)
    return users


def order_paid(order_id):
    """Given an order_id returns True if the order has been paid and
    False if it hasn't been paid.
    """
    woo = get_woo_connection()
    query = 'orders/{}'.format(order_id)
    order = woo.get(query).json()
    paid_statuses = ['completed', 'processing']
    if order and order['status'] in paid_statuses:
        return True
    return False


def can_change_bundle(user):
    if user.account_type == AccountType.INVITED:
        return False
    else:
        sub = active_subscription(user)
        if sub:
            choices = sub.upgrade_choices(add_downgrade=True)
        else:
            return False

        return len(choices) > 0


def active_subscription(user):
    """Returns currently active subscription for the user or None if the
    user does not have an active subscription.

    """
    subs = SubscriptionManager.search_subscriptions(
        user=user, status=SubscriptionStatus.ACTIVE
    )
    if len(subs) >= 1:
        return subs[0]
    else:
        return None


def invitees_clean(user):
    """For GROUP accounts returns True if all the invitees have set a
    recovery email and have one active Invitee subscription. Return
    False otherwise. For other type of accounts it always return True.

    """
    if user.account_type != AccountType.GROUP:
        return True
    else:
        for invitee in user.get_invitees():
            # Check if the invitee has an active Invitee subscription
            sub = active_subscription(invitee)
            if sub is None:
                # The invitee does not have an active account
                return False
            if (
                    sub.account_type != AccountType.INVITED
                    or invitee.email == ''
            ):
                return False
        return True
