from __future__ import unicode_literals
import frappe
from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection
import googleapiclient.discovery
import google.oauth2.credentials
from googleapiclient.errors import HttpError
import time
from datetime import datetime
from frappe.utils import add_days, add_years
from frappe.desk.doctype.event.event import has_permission

class CalendarConnector(BaseConnection):
	def __init__(self, connector):
		self.connector = connector
		settings = frappe.get_doc("GCalendar Settings", None)

		self.account = frappe.get_doc("GCalendar Account", connector.username)

		self.credentials_dict = {
			'token': self.account.get_password(fieldname='session_token', raise_exception=False),
			'refresh_token': self.account.get_password(fieldname='refresh_token', raise_exception=False),
			'token_uri': 'https://www.googleapis.com/oauth2/v4/token',
			'client_id': settings.client_id,
			'client_secret': settings.get_password(fieldname='client_secret', raise_exception=False),
			'scopes':'https://www.googleapis.com/auth/calendar'
			}

		self.name_field = 'id'

		self.credentials = google.oauth2.credentials.Credentials(**self.credentials_dict)
		self.gcalendar = googleapiclient.discovery.build('calendar', 'v3', credentials=self.credentials)

		self.check_remote_calendar()

	def check_remote_calendar(self):
		def _create_calendar():
			timezone = frappe.db.get_value("System Settings", None, "time_zone")
			calendar = {
				'summary': self.account.calendar_name,
				'timeZone': timezone
			}
			try:
				created_calendar = self.gcalendar.calendars().insert(body=calendar).execute()
				frappe.db.set_value("GCalendar Account", self.account.name, "gcalendar_id", created_calendar["id"])
			except Exception:
				frappe.log_error(frappe.get_traceback())
		try:
			if self.account.gcalendar_id is not None:
				try:
					self.gcalendar.calendars().get(calendarId=self.account.gcalendar_id).execute()
				except Exception:
					frappe.log_error(frappe.get_traceback())
			else:
				_create_calendar()
		except HttpError as err:
			if err.resp.status in [403, 500, 503]:
				time.sleep(5)
			elif err.resp.status in [404]:
				_create_calendar()
			else: raise


	def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10):
		return self.get_events(remote_objectname, filters, page_length)

	def insert(self, doctype, doc):
		if doctype == 'Events':
			d = frappe.get_doc("Event", doc["name"])
			if has_permission(d, self.account.name):
				try:
					doctype = "Event"
					e = self.insert_events(doctype, doc)
					return e
				except Exception:
					frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")


	def update(self, doctype, doc, migration_id):
		if doctype == 'Events':
			d = frappe.get_doc("Event", doc["name"])
			if has_permission(d, self.account.name):
				if migration_id is not None:
					try:
						doctype = "Event"
						return self.update_events(doctype, doc, migration_id)
					except Exception:
						frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")

	def delete(self, doctype, migration_id):
		if doctype == 'Events':
			try:
				return self.delete_events(migration_id)
			except Exception:
				frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")

	def get_events(self, remote_objectname, filters, page_length):
		page_token = None
		results = []
		events = {"items": []}
		while True:
			try:
				events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length,
					singleEvents=False, showDeleted=True, syncToken=self.account.next_sync_token or None).execute()
			except HttpError as err:
				if err.resp.status in [410]:
					events = self.gcalendar.events().list(calendarId=self.account.gcalendar_id, maxResults=page_length,
						singleEvents=False, showDeleted=True, timeMin=add_years(None, -1).strftime('%Y-%m-%dT%H:%M:%SZ')).execute()
				else:
					frappe.log_error(err.resp, "GCalendar Events Fetch Error")
			for event in events['items']:
				event.update({'account': self.account.name})
				event.update({'calendar_tz': events['timeZone']})
				results.append(event)

			page_token = events.get('nextPageToken')
			if not page_token:
				if events.get('nextSyncToken'):
					frappe.db.set_value("GCalendar Account", self.connector.username, "next_sync_token", events.get('nextSyncToken'))
				break
		return list(results)

	def insert_events(self, doctype, doc, migration_id=None):
		event = {
			'summary': doc.summary,
			'description': doc.description
		}

		dates = self.return_dates(doc)
		event.update(dates)

		if migration_id:
			event.update({"id": migration_id})

		if doc.repeat_this_event != 0:
			recurrence = self.return_recurrence(doctype, doc)
			if not not recurrence:
				event.update({"recurrence": ["RRULE:" + str(recurrence)]})

		try:
			remote_event = self.gcalendar.events().insert(calendarId=self.account.gcalendar_id, body=event).execute()
			return {self.name_field: remote_event["id"]}
		except Exception:
			frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error")

	def update_events(self, doctype, doc, migration_id):
		try:
			event = self.gcalendar.events().get(calendarId=self.account.gcalendar_id, eventId=migration_id).execute()
			event = {
				'summary': doc.summary,
				'description': doc.description
			}

			if doc.event_type == "Cancel":
				event.update({"status": "cancelled"})

			dates = self.return_dates(doc)
			event.update(dates)

			if doc.repeat_this_event != 0:
				recurrence = self.return_recurrence(doctype, doc)
				if recurrence:
					event.update({"recurrence": ["RRULE:" + str(recurrence)]})

			try:
				updated_event = self.gcalendar.events().update(calendarId=self.account.gcalendar_id, eventId=migration_id, body=event).execute()
				return {self.name_field: updated_event["id"]}
			except Exception as e:
				frappe.log_error(e, "GCalendar Synchronization Error")
		except HttpError as err:
			if err.resp.status in [404]:
				self.insert_events(doctype, doc, migration_id)
			else:
				frappe.log_error(err.resp, "GCalendar Synchronization Error")

	def delete_events(self, migration_id):
		try:
			self.gcalendar.events().delete(calendarId=self.account.gcalendar_id, eventId=migration_id).execute()
		except HttpError as err:
			if err.resp.status in [410]:
				pass

	def return_dates(self, doc):
		timezone = frappe.db.get_value("System Settings", None, "time_zone")
		if doc.end_datetime is None:
			doc.end_datetime = doc.start_datetime
		if doc.all_day == 1:
			return {
				'start': {
					'date': doc.start_datetime.date().isoformat(),
					'timeZone': timezone,
				},
				'end': {
					'date': add_days(doc.end_datetime.date(), 1).isoformat(),
					'timeZone': timezone,
				}
			}
		else:
			return {
				'start': {
					'dateTime': doc.start_datetime.isoformat(),
					'timeZone': timezone,
				},
				'end': {
					'dateTime': doc.end_datetime.isoformat(),
					'timeZone': timezone,
				}
			}

	def return_recurrence(self, doctype, doc):
		e = frappe.get_doc(doctype, doc.name)
		if e.repeat_till is not None:
			end_date = datetime.combine(e.repeat_till, datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ')
		else:
			end_date = None

		day = []
		if e.repeat_on == "Every Day":
			if e.monday == 1:
				day.append("MO")
			if e.tuesday == 1:
				day.append("TU")
			if e.wednesday == 1:
				day.append("WE")
			if e.thursday == 1:
				day.append("TH")
			if e.friday == 1:
				day.append("FR")
			if e.saturday == 1:
				day.append("SA")
			if e.sunday == 1:
				day.append("SU")

			day = "BYDAY=" + ",".join(str(d) for d in day)
			frequency = "FREQ=WEEKLY"

		elif e.repeat_on == "Every Week":
			frequency = "FREQ=WEEKLY"
		elif e.repeat_on == "Every Month":
			frequency = "FREQ=MONTHLY;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1"
			end_date = datetime.combine(add_days(e.repeat_till, 1), datetime.min.time()).strftime('UNTIL=%Y%m%dT%H%M%SZ')
		elif e.repeat_on == "Every Year":
			frequency = "FREQ=YEARLY"
		else:
			return None

		wst = "WKST=SU"

		elements = [frequency, end_date, wst, day]

		return ";".join(str(e) for e in elements if e is not None and not not e)
