import base64
import datetime
import urllib2

try:
    import json
except ImportError:
    import simplejson as json

import time
import vobject

from wcs.qommon import _
from wcs.qommon import get_cfg
from wcs.qommon.misc import format_time
from wcs.qommon.form import *

from wcs.data_sources import register_data_source_function
from wcs.formdata import Evolution
from wcs.forms.common import FormStatusPage
from wcs.workflows import Workflow, WorkflowStatusItem, register_item_class

def get_clicrdv_req(url):
    misc_cfg = get_cfg('misc', {})

    url = 'https://%s/api/v1/%s' % (
                misc_cfg.get('aq-clicrdv-server', 'sandbox.clicrdv.com'), url)
    if '?' in url:
       url = url + '&apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key')
    else:
       url = url + '?apikey=%s&format=json' % misc_cfg.get('aq-clicrdv-api-key')

    req = urllib2.Request(url)
    username = misc_cfg.get('aq-clicrdv-api-username')
    password = misc_cfg.get('aq-clicrdv-api-password')
    authheader = 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1]
    req.add_header('Authorization', authheader)
    return req

def get_json(url):
    return json.load(urllib2.urlopen(get_clicrdv_req(url)))

def as_str(s):
    if type(s) is unicode:
        return s.encode(get_publisher().site_charset)
    return s

def get_all_intervention_sets():
    interventions_set = []
    for interventionset in sorted(get_json('interventionsets').get('records'), 
            lambda x,y: cmp(x['sort'],y['sort'])):
        interventions = []
        for intervention in sorted(get_json('interventionsets/%s/interventions' % interventionset.get('id')).get('records'),
                lambda x,y: cmp(x['sort'], y['sort'])):
            if intervention.get('deleted') == True:
                continue
            name = '%s' % as_str(intervention.get('publicname'))
            if not name:
                name = '%s' % as_str(intervention.get('name'))
            interventions.append((intervention.get('id'), as_str(name)))
        interventions_set.append({
                'id': interventionset.get('id'),
                'group_id': interventionset.get('group_id'),
                'name': as_str(interventionset.get('name')),
                'publicname': as_str(interventionset.get('publicname')) or '',
                'description': as_str(interventionset.get('description')) or '',
                'interventions': interventions
                })
    return interventions_set

def get_all_interventions():
    interventions = []
    for s in get_all_intervention_sets():
        for i, publicname in s['interventions']:
            intervention_label = '%s - %s' % (s['publicname'], publicname)
            interventions.append((i, as_str(intervention_label)))
    return interventions

def get_interventions_in_set(interventionset_id):
    interventions = []
    interventions_json = get_json('interventionsets/%s/interventions' % interventionset_id)
    for intervention in interventions_json.get('records'):
        if intervention.get('deleted') != True:
            name = '%s' % as_str(intervention.get('publicname'))
            if not name:
                name = '%s' % as_str(intervention.get('name'))
            interventions.append((intervention.get('id'), name))
    return interventions

def get_available_timeslots(intervention, date_start=None, date_end=None):
    timeslots = []
    iid = intervention
    gid = get_json('interventions/%s' % iid).get('group_id')
    request_url = 'availabletimeslots?intervention_ids[]=%s&group_id=%s' % (iid, gid)
    if date_start is None:
        date_start = datetime.datetime.today().strftime('%Y-%m-%d')
    if date_end is None:
        date_end = (datetime.datetime.today() + datetime.timedelta(366)).strftime('%Y-%m-%d')
    if date_start:
        request_url = request_url + '&start=%s' % urllib2.quote(date_start)
    if date_end:
        request_url = request_url + '&end=%s' % urllib2.quote(date_end)
    for timeslot in get_json(request_url).get('availabletimeslots'):
        timeslots.append(timeslot.get('start'))
    timeslots.sort()
    return timeslots

def get_available_dates(intervention):
    dates = []
    for timeslot in get_available_timeslots(intervention):
        parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S')
        date_tuple = (time.strftime('%Y-%m-%d', parsed),
                      format_time(parsed, '%(weekday_name)s %(day)0.2d/%(month)0.2d/%(year)s'))
        if date_tuple in dates:
            continue
        dates.append(date_tuple)
    return dates

def get_available_times(intervention, date):
    times = []
    timeslots = get_available_timeslots(intervention,
                    date_start='%s 00:00:00' % date,
                    date_end='%s 23:59:59' % date)
    for timeslot in timeslots:
        parsed = time.strptime(timeslot, '%Y-%m-%d %H:%M:%S')
        time_tuple = (time.strftime('%H:%M:%S', parsed),
                      time.strftime('%Hh%M', parsed))
        times.append(time_tuple)
    times.sort()
    return times

register_data_source_function(get_all_interventions, 'clicrdv_get_all_interventions')
register_data_source_function(get_interventions_in_set, 'clicrdv_get_interventions_in_set')
register_data_source_function(get_available_dates, 'clicrdv_get_available_dates')
register_data_source_function(get_available_times, 'clicrdv_get_available_times')

def form_download_event(self):
    self.check_receiver()

    found = False
    for evo in self.filled.evolution:
        if evo.parts:
            for p in evo.parts:
                if not isinstance(p, AppointmentPart):
                    continue
                cal = vobject.iCalendar()
                cal.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
                vevent = vobject.newFromBehavior('vevent')
                vevent.add('uid').value = 'clicrdv-%s' % p.id
                vevent.add('summary').value = p.json_dict.get('group_name')
                vevent.add('dtstart').value = datetime.datetime.strptime(
                                p.json_dict.get('start'), '%Y-%m-%d %H:%M:%S')
                vevent.add('dtend').value = datetime.datetime.strptime(
                                p.json_dict.get('end'), '%Y-%m-%d %H:%M:%S')
                vevent.add('location').value = p.json_dict.get('location')
                cal.add(vevent)

                response = get_response()
                response.set_content_type('text/calendar')
                return cal.serialize()

    raise TraversalError()


class AppointmentPart(object):
    def __init__(self, json_dict):
        self.id = json_dict.get('id')
        self.json_dict = json_dict

    def view(self):
        return htmltext('<p class="appointment"><a href="clicrdvevent">%s</a></p>' % (
                                _('Download Appointment')))


class AppointmentErrorPart(object):
    def __init__(self, msg):
        self.msg = msg

    def view(self):
        return htmltext('<p class="appointment-error">%s</p>' % str(self.msg))


class ClicRdvCreateAppointment(WorkflowStatusItem):
    description = N_('Create a ClicRDV Appointment')
    key = 'clicrdv-create'
    category = 'interaction'

    endpoint = False

    var_firstname = None
    var_lastname = None
    var_email = None
    var_firstphone = None
    var_secondphone = None
    var_datetime = None
    var_intervention_id = None
    status_on_success = None
    status_on_failure = None

    def init(cls):
        FormStatusPage._q_extra_exports.append('clicrdvevent')
        FormStatusPage.clicrdvevent = form_download_event
    init = classmethod(init)

    def is_available(self, workflow=None):
        return get_publisher().has_site_option('clicrdv')
    is_available = classmethod(is_available)

    def render_as_line(self):
        return _('Create an appointment in ClicRDV')

    def get_parameters(self):
        return ('var_firstname', 'var_lastname', 'var_email', 'var_firstphone',
                'var_secondphone', 'var_datetime', 'var_intervention_id',
                'status_on_success', 'status_on_failure')

    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
        parameter_labels = {
            'var_firstname': N_('First Name'),
            'var_lastname': N_('Last Name'),
            'var_email': N_('Email'),
            'var_firstphone': N_('Phone (1st)'),
            'var_secondphone':  N_('Phone (2nd)'),
            'var_datetime': N_('Date/time'),
            'var_intervention_id': N_('Intervention Id'),
        }
        for parameter in self.get_parameters():
            if not parameter in parameter_labels:
                continue
            if parameter in parameters:
                form.add(StringWidget, '%s%s' % (prefix, parameter),
                         title=_(parameter_labels.get(parameter)),
                         value=getattr(self, parameter),
                         required=False)
        if 'status_on_success' in parameters:
            form.add(SingleSelectWidget, '%sstatus_on_success' % prefix,
                    title=_('Status On Success'), value=self.status_on_success,
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
        if 'status_on_failure' in parameters:
            form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix,
                    title=_('Status On Failure'), value=self.status_on_failure,
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])

    def perform(self, formdata):
        args = {}
        for parameter in self.get_parameters():
            args[parameter] = self.compute(getattr(self, parameter))
            if not args.get(parameter):
                del args[parameter]
        message = {'appointment':
                      {'fiche': {'firstname': args.get('var_firstname', '-'),
                                 'lastname': args.get('var_lastname', '-'),
                                 'email': args.get('var_email'),
                                 'firstphone': args.get('var_firstphone'),
                                 'secondphone': args.get('var_secondphone'),
                                },
                       'date': args.get('var_datetime'),
                       'intervention_ids': [int(args.get('var_intervention_id'))],
                       # 'comments': '-',
                       'websource': 'Publik'}
                  }

        req = get_clicrdv_req('appointments')
        req.add_data(json.dumps(message))
        req.add_header('Content-Type', 'application/json')

        try:
            fd = urllib2.urlopen(req)
        except urllib2.HTTPError, e:
            success = False
            try:
                msg = json.load(e.fp)[0].get('error')
            except:
                msg = _('unknown error')

            if formdata.evolution:
                evo = formdata.evolution[-1]
            else:
                formdata.evolution = []
                evo = Evolution()
                evo.time = time.localtime()
                evo.status = formdata.status
                formdata.evolution.append(evo)
            evo.add_part(AppointmentErrorPart(msg))
        else:
            success = True
            response = json.load(fd)
            appointment_id = response.get('records')[0].get('id')

            # add a message in formdata.evolution
            if formdata.evolution:
                evo = formdata.evolution[-1]
            else:
                formdata.evolution = []
                evo = Evolution()
                evo.time = time.localtime()
                evo.status = formdata.status
                formdata.evolution.append(evo)
            evo.add_part(AppointmentPart(response.get('records')[0]))

        formdata.store()

        if (success and self.status_on_success) or (success is False and self.status_on_failure):
            if success:
                formdata.status = 'wf-%s' % self.status_on_success
            else:
                formdata.status = 'wf-%s' % self.status_on_failure

register_item_class(ClicRdvCreateAppointment)


class ClicRdvCancelAppointment(WorkflowStatusItem):
    description = N_('Cancel a ClicRDV Appointment')
    key = 'clicrdv-cancel'
    category = 'interaction'

    endpoint = False

    status_on_success = None
    status_on_failure = None

    def get_parameters(self):
        return ('status_on_success', 'status_on_failure')

    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
        if 'status_on_success' in parameters:
            form.add(SingleSelectWidget, '%sstatus_on_success' % prefix,
                    title=_('Status On Success'), value=self.status_on_success,
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])
        if 'status_on_failure' in parameters:
            form.add(SingleSelectWidget, '%sstatus_on_failure' % prefix,
                    title=_('Status On Failure'), value=self.status_on_failure,
                    options = [(None, '---')] + [(x.id, x.name) for x in self.parent.parent.possible_status])

    def is_available(self, workflow=None):
        return get_publisher().has_site_option('clicrdv')
    is_available = classmethod(is_available)

    def render_as_line(self):
        return _('Cancel an appointment in ClicRDV')

    def perform(self, formdata):
        success = True

        for evo in [evo for evo in formdata.evolution if evo.parts]:
            for part in [part for part in evo.parts if isinstance(part, AppointmentPart)]:
                appointment_id = part.id
                try:
                    req = get_clicrdv_req('appointments/%s' % appointment_id)
                    req.get_method = (lambda: 'DELETE')
                    fd = urllib2.urlopen(req)
                    none = fd.read()
                except urllib2.URLError:
                    # clicrdv will return a "Bad Request" (HTTP 400) response
                    # when it's not possible to remove an appointment
                    # (for example because it's too late)
                    success = False

        if (success and self.status_on_success) or (success is False and self.status_on_failure):
            if success:
                formdata.status = 'wf-%s' % self.status_on_success
            else:
                formdata.status = 'wf-%s' % self.status_on_failure

register_item_class(ClicRdvCancelAppointment)
