import json
import sys
import time
import random
import requests
import datetime
from datetime import timedelta, datetime

from shared import DEBUG, RETRY, ORDER_NO_TYPE, left
from warranty_abstract import WarrantyBase

try:
    requests.packages.urllib3.disable_warnings()
except:
    pass


class Dell(WarrantyBase, object):
    def __init__(self, params):
        super(Dell, self).__init__()
        self.url = params['url']
        self.client_id = params['client_id']
        self.client_secret = params['client_secret']
        self.debug = DEBUG
        self.retry = RETRY
        self.order_no = ORDER_NO_TYPE
        self.d42_rest = params['d42_rest']
        self.common = None

        if self.order_no == 'common':
            self.common = self.generate_random_order_no()

        # OAuth 2.0
        self.expires_at = None
        self.access_token = None

    # OAth 2.0
    def get_access_token(self, client_id, client_secret):
        access_token_request_url = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token"

        timeout = 60

        payload = {
            'client_id': client_id,
            'client_secret': client_secret,
            'grant_type': 'client_credentials'
        }
        try:
            resp = requests.post(access_token_request_url, data=payload, timeout=timeout)

            msg = 'Status code: %s' % str(resp.status_code)

            if str(resp.status_code) == '400' or str(resp.status_code) == '401' or str(resp.status_code) == '404':
                print 'HTTP error. Message was: %s' % msg
            elif str(resp.status_code) == '500':
                print 'HTTP error. Message was: %s' % msg
                print 'token access services may be down, try again later...'
                print resp.text
            else:
                # assign access token and expiration to instance variables
                result = resp.json()
                self.access_token = "Bearer " + str(result['access_token'])
                self.expires_at = datetime.utcnow() + timedelta(seconds=int(result['expires_in']))
                if self.debug > 1:
                    print "Request Token Acquired"
        except requests.RequestException as e:
            self.error_msg(e)

    def run_warranty_check(self, inline_serials, retry=True):
        global full_serials
        full_serials = {}

        if self.debug:
            print '\t[+] Checking warranty info for "%s"' % inline_serials
        timeout = 60

        # making sure the warranty also gets updated if the serial has been changed by decom lifecycle process
        incoming_serials = inline_serials.split(',')
        inline_serials = []

        for d42_serial in incoming_serials:
            d42_serial = d42_serial.upper()
            if '_' in d42_serial:
                full_serials.update({d42_serial.split('_')[0]: d42_serial})
                d42_serial = d42_serial.split('_')[0]
            elif '(' in d42_serial:
                full_serials.update({d42_serial.split('(')[0]: d42_serial})
                d42_serial = d42_serial.split('(')[0]
            else:
                full_serials.update({d42_serial: d42_serial})
            inline_serials.append(d42_serial)
        inline_serials = ','.join(inline_serials)

        if self.expires_at is None or self.expires_at is not None and self.expires_at <= datetime.utcnow():
            if self.debug:
                print 'attempting to acquire access_token'

            self.get_access_token(self.client_id, self.client_secret)

        if self.access_token is None:
            if self.debug:
                print 'unable to acquire access_token'
            return None

        payload = {
            'servicetags': inline_serials,
            'Method': 'GET',
        }

        headers = {
            'Accept': 'Application/json',
            'Authorization': self.access_token
        }

        try:
            resp = requests.get(self.url, params=payload, headers=headers, verify=True, timeout=timeout)
            msg = 'Status code: %s' % str(resp.status_code)
            if str(resp.status_code) == '401' or str(resp.status_code) == '404':
                print '\t[!] HTTP error. Message was: %s' % msg
                print '\t[!] waiting for 30 seconds to let the api server calm down'
                # suspecting blockage due to to many api calls. Put in a pause of 30 seconds and go on
                time.sleep(30)
                if retry:
                    print '\n[!] Retry'
                    self.run_warranty_check(inline_serials, False)
                else:
                    return None
            else:
                result = resp.json()
                return result
        except requests.RequestException as e:
            self.error_msg(e)
            return None

    def process_result(self, result, purchases):
        global full_serials
        data = {}

        for item in result:
            try:
                warranties = item['entitlements']
            except IndexError:
                if self.debug:
                    try:
                        msg = str(result['InvalidFormatAssets']['BadAssets'])
                        if msg:
                            print '\t\t[-] Error: Bad asset: %s' % msg
                    except Exception as e:
                        print e

            else:
                if self.order_no == 'common':
                    order_no = self.common
                else:
                    order_no = self.generate_random_order_no()

                serial = item['serviceTag']

                # We need check per warranty service item
                for sub_item in warranties:
                    data.clear()

                    try:
                        if 'shipDate' in item:
                            ship_date = item['shipDate'].split('T')[0]
                        else:
                            ship_date = None
                    except AttributeError:
                        ship_date = None

                    data.update({'order_no': order_no})

                    if ship_date:
                        data.update({'po_date': ship_date})

                    data.update({'completed': 'yes'})

                    data.update({'vendor': 'Dell Inc.'})
                    data.update({'line_device_serial_nos': full_serials[serial]})
                    data.update({'line_type': 'contract'})
                    data.update({'line_item_type': 'device'})
                    data.update({'line_completed': 'yes'})

                    line_contract_id = sub_item['itemNumber']

                    data.update({'line_notes': line_contract_id})
                    data.update({'line_contract_id': line_contract_id})

                    # Using notes as well as the Device42 API doesn't give back the line_contract_id,
                    # so notes is now used for identification
                    # Mention this to device42

                    contract_type = 'Service'  # default contract type

                    if 'serviceLevelDescription' in sub_item:
                        if sub_item['serviceLevelDescription'] is not None:
                            if 'Parts' in sub_item['serviceLevelDescription'] or 'Onsite' in sub_item['serviceLevelDescription']:
                                contract_type = 'Warranty'
                        else:  # service level description is null, continue to next entitlement item
                            continue
                    else:  # service level description not listed in entitlement, continue to next item
                        continue

                    data.update({'line_contract_type': contract_type})

                    try:
                        # There's a max 64 character limit on the line service type field in Device42 (version 13.1.0)
                        service_level_description = left(sub_item['serviceLevelDescription'], 64)
                        data.update({'line_service_type': service_level_description})
                    except KeyError:
                        pass

                    # start date and end date may be missing from payload so it is only posted when both have values
                    try:
                        start_date = sub_item['startDate'].split('T')[0]
                        end_date = sub_item['endDate'].split('T')[0]
                        data.update({'line_start_date': start_date})
                        data.update({'line_end_date': end_date})
                    except AttributeError:
                        if self.debug:
                            print('[Alert]: SN:', serial, ': Missing start and end date for a listed entitlement')
                            print(left(sub_item['serviceLevelDescription'], 64))
                        continue

                    # update or duplicate? Compare warranty dates by serial, contract_id, start date and end date
                    hasher = serial + line_contract_id + start_date + end_date

                    try:
                        d_purchase_id, d_order_no, d_line_no, d_contractid, d_start, d_end, forcedupdate = purchases[hasher]

                        if forcedupdate:
                            data['purchase_id'] = d_purchase_id
                            data.pop('order_no')
                            raise KeyError

                        # check for duplicate state
                        if d_contractid == line_contract_id and d_start == start_date and d_end == end_date:
                            print '\t[!] Duplicate found. Purchase ' \
                                  'for SKU "%s" and "%s" with end date "%s" ' \
                                  'order_id: %s and line_no: %s' % (serial, line_contract_id, end_date, d_purchase_id, d_line_no)

                    except KeyError:
                        self.d42_rest.upload_data(data)
                        data.clear()