# -*- coding: utf-8 -*-
# Copyright 2020 Green Valley Belgium NV
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# @@license_version:1.7@@

from __future__ import unicode_literals

import base64
import logging
import re
from collections import defaultdict
from datetime import datetime
from types import NoneType

import cloudstorage
from babel import Locale
from babel.numbers import format_currency
from dateutil.relativedelta import relativedelta
from google.appengine.api import urlfetch
from google.appengine.api.taskqueue import taskqueue
from google.appengine.ext import db, deferred, ndb

from mcfw.consts import MISSING, REST_TYPE_TO
from mcfw.exceptions import HttpBadRequestException, HttpForbiddenException
from mcfw.properties import azzert, get_members
from mcfw.restapi import rest, GenericRESTRequestHandler
from mcfw.rpc import returns, arguments, serialize_complex_value
from rogerthat.bizz.forms import FormNotFoundException
from rogerthat.bizz.gcs import get_serving_url
from rogerthat.bizz.maps.poi.models import PointOfInterest
from rogerthat.bizz.maps.services.places import get_place_types
from rogerthat.bizz.rtemail import EMAIL_REGEX
from rogerthat.bizz.service import AvatarImageNotSquareException, InvalidValueException
from rogerthat.consts import DEBUG, SCHEDULED_QUEUE
from rogerthat.dal import parent_key, put_and_invalidate_cache, parent_key_unsafe, put_in_chunks, parent_ndb_key
from rogerthat.dal.profile import get_user_profile, get_profile_key, get_service_profile
from rogerthat.models import ServiceIdentity
from rogerthat.models.news import MediaType
from rogerthat.rpc import users
from rogerthat.rpc.service import BusinessException
from rogerthat.rpc.users import get_current_session
from rogerthat.service.api import system
from rogerthat.settings import get_server_settings
from rogerthat.to import ReturnStatusTO, RETURNSTATUS_TO_SUCCESS
from rogerthat.to.friends import FriendListResultTO
from rogerthat.to.messaging import BaseMemberTO
from rogerthat.to.service import UserDetailsTO
from rogerthat.translations import DEFAULT_LANGUAGE
from rogerthat.utils.app import get_human_user_from_app_user, sanitize_app_user, \
    get_app_id_from_app_user, get_app_user_tuple
from rogerthat.utils.channel import send_message
from rogerthat.utils.service import create_service_identity_user, remove_slash_default
from shop.bizz import add_service_admin, get_service_admins
from shop.dal import get_customer, get_customer_signups
from shop.exceptions import InvalidEmailFormatException
from shop.models import Customer, CustomerSignup
from solutions import translate as common_translate
from solutions.common.bizz import get_next_free_spots_in_service_menu, common_provision, timezone_offset, \
    broadcast_updates_pending, SolutionModule, delete_file_blob, create_file_blob, \
    create_news_publisher, delete_news_publisher, enable_or_disable_solution_module, \
    validate_enable_or_disable_solution_module, OrganizationType, get_organization_type, validate_before_provision, \
    auto_publish
from solutions.common.bizz.branding_settings import save_branding_settings
from solutions.common.bizz.events import update_events_from_google, get_google_authenticate_url, get_google_calendars
from solutions.common.bizz.group_purchase import save_group_purchase, delete_group_purchase, broadcast_group_purchase, \
    new_group_purchase_subscription
from solutions.common.bizz.images import upload_file, list_files
from solutions.common.bizz.inbox import send_statistics_export_email, send_inbox_info_messages_to_services
from solutions.common.bizz.loyalty import update_user_data_admins
from solutions.common.bizz.menu import _put_default_menu, get_menu_item_qr_url
from solutions.common.bizz.messaging import validate_broadcast_url, send_reply, delete_all_trash
from solutions.common.bizz.paddle import get_paddle_info, populate_info_from_paddle
from solutions.common.bizz.repair import send_message_for_repair_order, delete_repair_order
from solutions.common.bizz.sandwich import ready_sandwich_order, delete_sandwich_order, reply_sandwich_order
from solutions.common.bizz.service import new_inbox_message, send_inbox_message_update, set_customer_signup_status
from solutions.common.bizz.settings import save_settings, set_logo, set_avatar, save_rss_urls, get_service_info
from solutions.common.bizz.static_content import put_static_content as bizz_put_static_content, delete_static_content
from solutions.common.consts import TRANSLATION_MAPPING, OCA_FILES_BUCKET, AUTO_PUBLISH_MINUTES
from solutions.common.dal import get_solution_settings, get_static_content_list, get_solution_group_purchase_settings, \
    get_solution_calendars, get_solution_inbox_messages, \
    get_solution_identity_settings, get_solution_settings_or_identity_settings, \
    get_solution_news_publishers, is_existing_friend
from solutions.common.dal.appointment import get_solution_appointment_settings
from solutions.common.dal.repair import get_solution_repair_orders, get_solution_repair_settings
from solutions.common.integrations.jcc.jcc_appointments import get_jcc_settings, save_jcc_settings
from solutions.common.integrations.qmatic.qmatic import get_qmatic_settings, save_qmatic_settings, QMaticSettingsTO
from solutions.common.integrations.timeblockr.settings import get_timeblockr_settings, save_timeblockr_settings
from solutions.common.localizer import translations
from solutions.common.models import SolutionBrandingSettings, SolutionSettings, SolutionInboxMessage, \
    SolutionRssScraperSettings
from solutions.common.models.agenda import SolutionCalendar
from solutions.common.models.appointment import SolutionAppointmentWeekdayTimeframe, SolutionAppointmentSettings
from solutions.common.models.cityapp import PaddleSettings, PaddleMapping, PaddleOrganizationalUnits
from solutions.common.models.forms import OcaForm
from solutions.common.models.group_purchase import SolutionGroupPurchase
from solutions.common.models.repair import SolutionRepairSettings
from solutions.common.models.sandwich import SandwichType, SandwichTopping, SandwichOption, SandwichSettings, \
    SandwichOrder
from solutions.common.models.static_content import SolutionStaticContent
from solutions.common.to import ServiceMenuFreeSpotsTO, SolutionStaticContentTO, SolutionSettingsTO, \
    MenuTO, EventItemTO, PublicEventItemTO, SolutionAppointmentWeekdayTimeframeTO, BrandingSettingsTO, \
    SolutionRepairOrderTO, SandwichSettingsTO, SandwichOrderTO, SolutionGroupPurchaseTO, \
    SolutionGroupPurchaseSettingsTO, SolutionCalendarTO, SolutionInboxForwarder, SolutionInboxesTO, \
    SolutionInboxMessageTO, SolutionAppointmentSettingsTO, \
    SolutionRepairSettingsTO, UrlReturnStatusTO, ImageReturnStatusTO, SolutionUserKeyLabelTO, \
    SolutionCalendarWebTO, BrandingSettingsAndMenuItemsTO, ServiceMenuItemWithCoordinatesTO, \
    ServiceMenuItemWithCoordinatesListTO, SolutionGoogleCalendarStatusTO, PictureReturnStatusTO, \
    AppUserRolesTO, CustomerSignupTO, SolutionRssSettingsTO, CreateEventItemTO
from solutions.common.to.forms import UploadedFileTO, GalleryFileTO
from solutions.common.to.paddle import PaddleSettingsTO, PaddleSettingsServicesTO, SimpleServiceTO
from solutions.common.to.statistics import StatisticsResultTO
from solutions.common.utils import is_default_service_identity, create_service_identity_user_wo_default


@rest("/solutions/common/public/menu/load", "get", authenticated=False)
@returns(dict)
@arguments(service_user_email=unicode)
def public_load_menu(service_user_email):
    from solutions.common.dal import get_restaurant_menu

    if service_user_email in (None, MISSING):
        logging.debug("Could not load public menu (service_user_email None|MISSING)")
        return None

    service_user = users.User(service_user_email)
    sln_settings = get_solution_settings(service_user)
    if not sln_settings:
        logging.debug("Could not load public menu for: %s (SolutionSettings==None)", service_user_email)
        return None

    menu = get_restaurant_menu(service_user, sln_settings.solution)
    if not menu:
        logging.debug("Could not load public menu for: %s (Menu==None)", service_user_email)
        return None

    menu = serialize_complex_value(MenuTO.fromMenuObject(menu), MenuTO, False)
    # convert prices from long to unicode
    for category in menu['categories']:
        for item in category['items']:
            item['price'] = format_currency((item['price'] or 0) / 100.0, sln_settings.currency, '#,##0.00',
                                            locale=sln_settings.main_language)
    return menu


@rest("/solutions/common/public/events/load", "get", authenticated=False)
@returns([PublicEventItemTO])
@arguments(service_user_email=unicode)
def public_load_events(service_user_email):
    from solutions.common.dal import get_public_event_list

    if service_user_email and service_user_email != MISSING:
        service_user = users.User(service_user_email)
        return [PublicEventItemTO.fromPublicEventItemObject(e) for e in get_public_event_list(service_user)]
    else:
        logging.debug("Could not load public events (service_user_email None|MISSING)")
    return None


@rest("/solutions/common/public/group_purchase/picture", "get", authenticated=False, silent_result=True)
@returns(PictureReturnStatusTO)
@arguments(service_user_email=unicode, group_purchase_id=long, picture_version=long, service_identity=unicode)
def public_group_purchase_picture(service_user_email, group_purchase_id, picture_version=0, service_identity=None):
    service_user = users.User(service_user_email)
    settings = get_solution_settings(service_user)
    service_identity_user = create_service_identity_user_wo_default(service_user, service_identity)
    sgp = SolutionGroupPurchase.get_by_id(group_purchase_id,
                                          parent_key_unsafe(service_identity_user, settings.solution))
    if not sgp or not sgp.picture:
        return PictureReturnStatusTO.create(False, None)

    response = GenericRESTRequestHandler.getCurrentResponse()
    response.headers['Cache-Control'] = "public, max-age=31536000"  # Cache forever (1 year)
    response.headers['Access-Control-Allow-Origin'] = '*'
    return PictureReturnStatusTO.create(picture=unicode(sgp.picture))


@rest("/common/service_menu/get_free_spots", "get", read_only_access=True)
@returns(ServiceMenuFreeSpotsTO)
@arguments(count=int)
def get_free_spots(count=10):
    service_user = users.get_current_user()
    sm = system.get_menu()
    all_taken_coords = [item.coords for item in sm.items]
    static_content_items = SolutionStaticContent.list_changed(service_user)
    for sc in static_content_items:
        if sc.deleted or not sc.visible:
            try:
                all_taken_coords.remove(sc.coords)
            except ValueError:
                pass
        else:  # not deleted and visible
            all_taken_coords.append(sc.coords)
    spots = get_next_free_spots_in_service_menu(all_taken_coords, count)
    return ServiceMenuFreeSpotsTO.fromList(spots)


@rest("/common/static_content/load", "get", read_only_access=True, silent_result=True)
@returns([SolutionStaticContentTO])
@arguments()
def get_static_content():
    service_user = users.get_current_user()
    static_contents = sorted(get_static_content_list(service_user), key=lambda m: tuple(reversed(m.coords)))
    return [SolutionStaticContentTO.fromModel(sc) for sc in static_contents]


@rest("/common/static_content/put", "post")
@returns(ReturnStatusTO)
@arguments(static_content=SolutionStaticContentTO)
def put_static_content(static_content):
    service_user = users.get_current_user()
    try:
        bizz_put_static_content(service_user, static_content)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/static_content/delete", "post")
@returns(ReturnStatusTO)
@arguments(static_content_id=(int, long, NoneType))
def rest_delete_static_content(static_content_id=None):
    service_user = users.get_current_user()
    try:
        delete_static_content(service_user, static_content_id)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/inbox/load/all", "get", read_only_access=True)
@returns([SolutionInboxesTO])
@arguments()
def inbox_load_all():
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    sln_settings = get_solution_settings(service_user)
    service_info = get_service_info(service_user, service_identity)

    unread_cursor, unread_messages, unread_has_more = get_solution_inbox_messages(service_user,
                                                                                  service_identity,
                                                                                  10,
                                                                                  SolutionInboxMessage.INBOX_NAME_UNREAD)
    starred_cursor, starred_messages, starred_has_more = get_solution_inbox_messages(service_user,
                                                                                     service_identity,
                                                                                     10,
                                                                                     SolutionInboxMessage.INBOX_NAME_STARRED)
    read_cursor, read_messages, read_has_more = get_solution_inbox_messages(service_user,
                                                                            service_identity,
                                                                            10,
                                                                            SolutionInboxMessage.INBOX_NAME_READ)
    trash_cursor, trash_messages, trash_has_more = get_solution_inbox_messages(service_user,
                                                                               service_identity,
                                                                               10,
                                                                               SolutionInboxMessage.INBOX_NAME_TRASH)

    inboxes = [(SolutionInboxMessage.INBOX_NAME_UNREAD, unread_cursor, unread_messages, unread_has_more),
               (SolutionInboxMessage.INBOX_NAME_STARRED, starred_cursor, starred_messages, starred_has_more),
               (SolutionInboxMessage.INBOX_NAME_READ, read_cursor, read_messages, read_has_more),
               (SolutionInboxMessage.INBOX_NAME_TRASH, trash_cursor, trash_messages, trash_has_more)]

    return [SolutionInboxesTO.fromModel(name, cursor, messages, has_more, sln_settings, service_info, True) for
            name, cursor, messages, has_more in inboxes]


@rest("/common/inbox/load/more", "get", read_only_access=True)
@returns(SolutionInboxesTO)
@arguments(name=unicode, count=(int, long), cursor=unicode)
def inbox_load_more(name, count, cursor):
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    sln_settings = get_solution_settings(service_user)
    service_info = get_service_info(service_user, service_identity)
    cursor_, messages, has_more = get_solution_inbox_messages(service_user, service_identity, count, name, cursor)
    return SolutionInboxesTO.fromModel(name, cursor_, messages, has_more, sln_settings, service_info, True)


@rest("/common/inbox/load/detail", "get", read_only_access=True)
@returns([SolutionInboxMessageTO])
@arguments(key=unicode)
def inbox_load_detail(key):
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    sln_settings = get_solution_settings(service_user)
    sim = SolutionInboxMessage.get(key)
    messages = [sim]
    messages.extend(sim.get_child_messages())
    service_info = get_service_info(service_user, service_identity)
    return [SolutionInboxMessageTO.fromModel(message, sln_settings, service_info, False) for message in messages]


@rest("/common/inbox/message/update/reply", "post")
@returns(ReturnStatusTO)
@arguments(key=unicode, message=unicode)
def inbox_message_update_reply(key, message):
    service_user = users.get_current_user()
    try:
        send_reply(service_user, key, message)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/inbox/message/forward", "post")
@returns(ReturnStatusTO)
@arguments(key=unicode, to_email=unicode)
def inbox_message_forward(key, to_email):
    """Only city service to community service"""
    try:
        sim = SolutionInboxMessage.get(key)
        to_service_user = users.User(to_email)
        to_sln_settings = get_solution_settings(to_service_user)
        new_sim = new_inbox_message(to_sln_settings, sim.message,
                                    user_details=sim.get_sender().to_user_details(),
                                    category=sim.category,
                                    category_key=sim.category_key,
                                    reply_enabled=sim.reply_enabled,
                                    send_to_forwarders=True)
        send_inbox_message_update(to_sln_settings, new_sim)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as ex:
        return ReturnStatusTO.create(False, ex.message)


@rest("/common/inbox/message/update/starred", "post")
@returns(ReturnStatusTO)
@arguments(key=unicode, starred=bool)
def inbox_message_update_starred(key, starred):
    inbox_message = SolutionInboxMessage.get(key)
    inbox_message.starred = starred
    inbox_message.put()
    return _after_inbox_message_updated(inbox_message)


@rest("/common/inbox/message/update/read", "post")
@returns(ReturnStatusTO)
@arguments(key=unicode, read=bool)
def inbox_message_update_read(key, read):
    inbox_message = SolutionInboxMessage.get(key)
    inbox_message.read = read
    inbox_message.put()
    return _after_inbox_message_updated(inbox_message)


def _after_inbox_message_updated(inbox_message):
    # type: (SolutionInboxMessage) -> ReturnStatusTO
    try:
        service_user = users.get_current_user()
        service_identity = users.get_current_session().service_identity
        sln_settings = get_solution_settings(service_user)
        service_info = get_service_info(service_user, service_identity)
        send_message(service_user, u"solutions.common.messaging.update",
                     service_identity=service_identity,
                     message=SolutionInboxMessageTO.fromModel(inbox_message, sln_settings, service_info,
                                                              True).to_dict())
        deferred.defer(update_user_data_admins, service_user, service_identity)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/inbox/message/update/trashed", "post")
@returns(ReturnStatusTO)
@arguments(key=unicode, trashed=bool)
def inbox_message_update_trashed(key, trashed):
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    sln_settings = get_solution_settings(service_user)
    service_info = get_service_info(service_user, service_identity)
    try:
        sim = SolutionInboxMessage.get(key)
        if trashed and sim.trashed:
            sim.deleted = True
        sim.trashed = trashed
        sim.put()
        send_message(service_user, u"solutions.common.messaging.update",
                     service_identity=service_identity,
                     message=SolutionInboxMessageTO.fromModel(sim, sln_settings, service_info, True).to_dict())
        if not sim.deleted:
            deferred.defer(update_user_data_admins, service_user, service_identity)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/inbox/message/update/deleted", "post")
@returns(ReturnStatusTO)
@arguments()
def inbox_message_update_deleted():
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    try:
        delete_all_trash(service_user, service_identity)
        return RETURNSTATUS_TO_SUCCESS
    except BusinessException as e:
        return ReturnStatusTO.create(False, e.message)


@rest("/common/inbox/messages/export", "get")
@returns(ReturnStatusTO)
@arguments(email=unicode)
def export_inbox_messages(email=''):
    service_user = users.get_current_user()
    session_ = users.get_current_session()
    service_identity = session_.service_identity
    sln_settings = get_solution_settings(service_user)
    try:
        send_statistics_export_email(service_user, service_identity, email, sln_settings)
        return RETURNSTATUS_TO_SUCCESS
    except InvalidEmailFormatException as ex: