#!/usr/bin/env python
# coding=utf-8
import json
import datetime

from django.db.models import Q

from services.notify import notify
from gm_types.gaia import RESERVATION_TYPE, RESERVATION_STATUS, TIME_TYPE
from api.tool.user_tool import get_user_from_context
from api.tool.datetime_tool import get_timestamp
from api.tool.log_tool import logging_exception

from api.tasks.reservation_task import doctor_confirm_reservation, doctor_cancel_reservation

from rpc.decorators import bind_context
from rpc.decorators import list_interface
from rpc.tool.dict_mixin import to_dict
from rpc.tool.error_code import CODES, gen

from services.doctor_notify_service import DoctorNotifyService

from hippo.tool.user_tool import get_doctor_by_user_id, get_doctor_from_context
from hippo.models.reservation import Reservation, DoctorAddress, Schedule, ScheduleTimeSlot, ScheduleTemplate

AM_BEGIN_TIME = '08:00:00'
AM_END_TIME = '12:00:00'
PM_BEGIN_TIME = '14:00:00'
PM_END_TIME = '18:00:00'


@bind_context('doctor/reserve/statistics', login_required=True)
def reserve_manage_stat(ctx, reservation_type=None, status=None):
    """预约数目统计
    """
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)
    reservations = Reservation.objects.filter(schedule__doctor_id=doctor.id)
    if reservation_type is not None:
        reservations = reservations.filter(reservation_type=reservation_type)
    result = {}
    if status is None or status == RESERVATION_STATUS.RESERVING:
        result['reserving_num'] = reservations.filter(status=RESERVATION_STATUS.RESERVING).count()
    if status is None or status == RESERVATION_STATUS.ACCEPTED:
        result['reserve_accepted_num'] = reservations.filter(status=RESERVATION_STATUS.ACCEPTED).count()
    if status is None or status == RESERVATION_STATUS.CANCELED:
        result['reserve_canceled'] = reservations.filter(status=RESERVATION_STATUS.CANCELED).count()
    if status is None or status == RESERVATION_STATUS.EXPIRED:
        result['reserve_expired'] = reservations.filter(status=RESERVATION_STATUS.EXPIRED).count()
    return result


@bind_context('doctor/reserve/list', login_required=True)
@list_interface(offset_name='offset', limit_name='limit', element_model=Reservation)
def reserve_list(ctx, offset=0, limit=30, reservation_type=None, status=None, reserve_date=None):
    """预约列表

    :param offset: 起始位置
    :param limit: 限制条数
    :param reserve_date: 预约的日期, '2016-01-13'
    """

    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    # 检测过期的预约
    if Reservation.doctor_check_reservation_expired(doctor) > 0:
        from api.tool.log_tool import doctor_unread_logger
        doctor_unread_logger.info("check_reservation_expired, doctor_id:{}".format(doctor.id))
        # add 医生版实时小红点数量
        notify("reserve/delete", doctor_id=doctor.id)

    reservations = Reservation.objects.filter(
        schedule__doctor_id=doctor.id,
    )
    if reservation_type is not None:
        reservations = reservations.filter(reservation_type=reservation_type)
    if reserve_date is not None:
        DATE_FMT = '%Y-%m-%d'
        try:
            start_date = datetime.datetime.strptime(reserve_date, DATE_FMT)
            end_date = start_date + datetime.timedelta(days=1)
            reservations = reservations.filter(date__range=(start_date, end_date))
        except:
            logging_exception()
            raise
    if status is None:
        reservations = reservations.order_by('-date')
    elif isinstance(status, list):
        reservations = reservations.filter(status__in=status)
        reservations = reservations.order_by('date')
    else:
        reservations = reservations.filter(status=status)
        if status in [RESERVATION_STATUS.RESERVING, RESERVATION_STATUS.ACCEPTED]:
            reservations = reservations.order_by('date')
        else:
            reservations = reservations.order_by('-date')

    result = []
    for reservation in reservations[offset: offset + limit]:
        result.append({
            'id': reservation.id,
            'timestamp': get_timestamp(reservation.date),
            'date': reservation.date.strftime('%Y-%m-%d'),
            'time': reservation.date.strftime('%H:%M'),
            'type': RESERVATION_TYPE.getDesc(reservation.reservation_type),
            'service': reservation.order.service.name,
            'user': {
                'id': reservation.user.id,
                'name': reservation.user.last_name,
            },
            'address': reservation.schedule.address.desc,
            'status': reservation.status,
        })
    data = {
        'count': reservations.count(),
        'data': result,
    }

    return data


@bind_context('doctor/reserve/enable', login_required=True)
def reserve_enable(ctx):
    """开启/关闭预约功能
    """
    # TODO Deprecated since 医生版2.0.0, 都默认开启
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    if not doctor.accept_reserve:
        doctor.accept_reserve = True
        doctor.save()
        return {'success': True, 'accept_reserve': doctor.accept_reserve}

    # 已开启，需要关闭
    # 检查是否有预约未执行
    now_t = datetime.datetime.now()
    if Reservation.objects.filter(
            schedule__doctor_id=doctor.id, date__gt=now_t,
            status=RESERVATION_STATUS.ACCEPTED).exists():
        # 医生版1.5.0需求， 判断医生有没有“已确认”状态的预约进行关闭
        # 还有预约，不允许关闭
        return {'success': False, 'accept_reserve': doctor.accept_reserve}
    doctor.accept_reserve = False
    doctor.save()
    return {'success': True, 'accept_reserve': doctor.accept_reserve}


@bind_context('doctor/reserve/set_status', login_required=True)
def reserve_set_status(ctx, reserve_id, status, cancel_reason=None):
    """设置预约状态, 接受/拒绝/取消
    """
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    try:
        reserve = Reservation.objects.get(id=reserve_id, schedule__doctor_id=doctor.id)
    except:
        # TODO raise not found
        logging_exception()
        raise
    old_status = str(reserve.status)
    new_status = str(status)

    reserve.status = status

    ss = DoctorNotifyService.get_service(doctor.id)

    if new_status == RESERVATION_STATUS.ACCEPTED:
        reserve.accepted_time = datetime.datetime.now()

        ss.notify_confirm_reservation(reserve)
    elif new_status == RESERVATION_STATUS.CANCELED:
        reserve.cancel_operator_role_id = user
        reserve.cancel_time = datetime.datetime.now()
        if cancel_reason:
            reserve.cancel_reason = cancel_reason[:100]

        ss.notify_cancel_reservation(reserve)
    reserve.save()

    try:
        if old_status == RESERVATION_STATUS.RESERVING and new_status == RESERVATION_STATUS.ACCEPTED:
            # 预约被确认
            doctor_confirm_reservation(reserve)
        if old_status in (RESERVATION_STATUS.RESERVING, RESERVATION_STATUS.ACCEPTED) \
                and new_status == RESERVATION_STATUS.CANCELED:
            # 预约被取消
            ScheduleTimeSlot.objects.filter(reservation_id=reserve.id).update(reservation_id=None)
            doctor_cancel_reservation(reserve)
    except:
        __import__('traceback').print_exc()
        logging_exception()

    # 9.19 add 医生小红点数量
    notify("reserve/delete", doctor_id=doctor.id)
    return True


@bind_context('doctor/schedule/list', login_required=True)
def schedule_list(ctx, begin_date, end_date):
    """医生schedule 列表
    """
    # TODO Deprecated since 重构，see doctor/schedule/client_list
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    # 检查生成默认地址
    insert_address_if_empty(doctor)

    default_address = DoctorAddress.objects.filter(doctor_id=doctor.id).first()

    # 获取预约信息，如果没有当天上午/下午的时间信息，默认创建一个
    DATE_FMT = '%Y-%m-%d'
    begin_d = datetime.datetime.strptime(begin_date, DATE_FMT)
    end_d = datetime.datetime.strptime(end_date, DATE_FMT)
    date = begin_d
    infos = []
    options = {
        'fields': None,
        'excludes': None,
        'expands': None,
    }
    while True:
        for time_type in ['AM', 'PM']:
            start_time = globals().get('%s_BEGIN_TIME' % (time_type), '')
            end_time = globals().get('%s_END_TIME' % (time_type), '')
            schedule, _ = Schedule.objects.get_or_create(
                doctor_id=doctor.id,
                date=date,
                am_or_pm=getattr(TIME_TYPE, time_type),
                defaults={
                    'start_time': start_time,
                    'end_time': end_time,
                    'address': default_address,
                    'reservation_type': '',
                },
            )
            infos.append(to_dict(schedule, **options))
        date += datetime.timedelta(days=1)
        if date >= end_d:
            break
    return infos


@bind_context('doctor/schedule/save', login_required=True)
def schedule_save(ctx, schedules_info=[], template_id=None, first_day=None):
    """保存医生schedule 的编辑
        1. 根据数据直接保存schedule
            只传递参数schedules_info
        2. 根据模板导入 一周的时间表
            template_id 模板的id
            first_day 一周的第一天
    """
    doctor = get_doctor_from_context(ctx)

    if template_id:
        schedules_info = []
        DATE_FMT = '%Y-%m-%d'
        first_day = datetime.datetime.strptime(first_day, DATE_FMT)
        template = ScheduleTemplate.objects.get(id=template_id)
        content = json.loads(template.content)  # content的格式可详见model中定义
        for day_info in content:
            am_info = day_info['am']
            schedules_info.append({
                'date': first_day.strftime(DATE_FMT),
                'start_time': am_info['start_time'],
                'end_time': am_info['end_time'],
                'address_id': am_info['address_id'],
                'reservation_type': am_info['reservation_type'],
                'am_or_pm': TIME_TYPE.AM,
            })
            pm_info = day_info['pm']
            schedules_info.append({
                'date': first_day.strftime(DATE_FMT),
                'start_time': pm_info['start_time'],
                'end_time': pm_info['end_time'],
                'address_id': pm_info['address_id'],
                'reservation_type': pm_info['reservation_type'],
                'am_or_pm': TIME_TYPE.PM,
            })
            first_day += datetime.timedelta(days=1)

    for info in schedules_info:
        keys = ['start_time', 'end_time', 'address_id', 'reservation_type']
        default_info = {k: info[k] for k in keys}
        try:
            DoctorAddress.objects.get(
                doctor_id=doctor.id,
                id=info['address_id'],
            )
        except:
            logging_exception()
            raise
        schedule, _ = Schedule.objects.get_or_create(
            doctor=doctor,
            date=info['date'],
            am_or_pm=info['am_or_pm'],
            defaults=default_info,
        )
        for k in keys:
            setattr(schedule, k, info[k])
        schedule.save()
        ScheduleTimeSlot.objects.init(Schedule.objects.get(id=schedule.id))
    return True


def insert_address_if_empty(doctor):
    if not DoctorAddress.objects.filter(doctor_id=doctor.id).exists():
        # 没有地址，从医生的医院里面取
        hospital = getattr(doctor, 'hospital', None)
        if not hospital:
            return
        DoctorAddress.objects.create(
            doctor=doctor,
            desc=hospital.name,
            location=hospital.location,
            lng=hospital.baidu_loc_lng,
            lat=hospital.baidu_loc_lat,
        )


@bind_context('doctor/schedule/client_list', login_required=True)
def schedule_list_for_client(ctx, begin_date, end_date, use_template=True):
    """医生schedule 列表(for client)
        begin_date和end_date格式为%Y-%m-%d
        begin_date 周一00:00
        end_date 周日23:59
        use_template  标志是否导入模板, client有导入模板，web没有
    """
    doctor = get_doctor_from_context(ctx)
    DATE_FMT = '%Y-%m-%d'
    TIME_FMT = '%H:%M'
    begin_d = datetime.datetime.strptime(begin_date, DATE_FMT)
    end_d = datetime.datetime.strptime(end_date, DATE_FMT)

    # 检查生成默认地址
    insert_address_if_empty(doctor)
    default_address = DoctorAddress.objects.filter(doctor_id=doctor.id).first()

    # 当每周显示的时候，显示导入模板的按钮
    show_template = False
    total_day = (end_d - begin_d).days
    infos = []
    while begin_d <= end_d:
        item = {
            'id': begin_d.strftime(DATE_FMT),
            'title': begin_d.strftime('%m月%d日'),
            'am': {
                'start_time': AM_BEGIN_TIME[:5],
                'end_time': AM_END_TIME[:5],
                'address_desc': default_address.desc,
                'address_id': default_address.id,
                'reservation_type': '',
            },
            'pm': {
                'start_time': PM_BEGIN_TIME[:5],
                'end_time': PM_END_TIME[:5],
                'address_desc': default_address.desc,
                'address_id': default_address.id,
                'reservation_type': '',
            },
        }
        try:
            am_s = Schedule.objects.select_related('address').get(
                doctor_id=doctor.id,
                date=begin_d,
                am_or_pm=TIME_TYPE.AM,
            )
            item['am'] = {
                'start_time': am_s.start_time.strftime(TIME_FMT),
                'end_time': am_s.end_time.strftime(TIME_FMT),
                'address_desc': am_s.address.desc,
                'address_id': am_s.address.id,
                'reservation_type': am_s.reservation_type,
            }
        except Schedule.DoesNotExist:
            show_template = True
        try:
            pm_s = Schedule.objects.select_related('address').get(
                doctor_id=doctor.id,
                date=begin_d,
                am_or_pm=TIME_TYPE.PM,
            )
            item['pm'] = {
                'start_time': pm_s.start_time.strftime(TIME_FMT),
                'end_time': pm_s.end_time.strftime(TIME_FMT),
                'address_desc': pm_s.address.desc,
                'address_id': pm_s.address.id,
                'reservation_type': pm_s.reservation_type,
            }
        except Schedule.DoesNotExist:
            show_template = True

        if not use_template:  # 不是用模板，返回默认数据
            pass
        elif total_day == 6 and show_template:  # 按周显示的时候需要导入模板
            infos = []
            break
        infos.append(item)
        begin_d += datetime.timedelta(days=1)
    return infos


@bind_context('doctor/address/choices', login_required=True)
def address_choices(ctx, q='', page=1, start_num=None, num=30, initial=None):
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    # 检查生成默认地址
    insert_address_if_empty(doctor)

    page = int(page)
    num = int(num)

    if initial is not None:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    else:
        qry = Q(id__contains=q) | Q(desc__contains=q)
    qry &= Q(doctor_id=doctor.id)
    query = DoctorAddress.objects.filter(qry).order_by('-updated_time')
    total_count = query.count()
    if start_num is None:
        start_pos = (page - 1) * num
        start_pos = start_pos if start_pos >= 0 else 0
    else:
        start_pos = start_num
    results = [
        {
            'id': obj.id,
            'text': u'{}'.format(obj.desc),
            'desc': u'{}'.format(obj.desc),
            'lng': obj.lng,
            'lat': obj.lat,
            'location': obj.location,
        } for obj in query[start_pos: start_pos + num]
    ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('doctor/address/create', login_required=True)
def address_create(ctx, address_info):
    user = get_user_from_context(ctx)
    doctor = get_doctor_by_user_id(user.id)

    try:
        address = DoctorAddress.objects.create(
            desc=address_info['desc'],
            lng=address_info['lng'],
            lat=address_info['lat'],
            doctor=doctor,
            location=address_info['location'],
        )
    except:
        logging_exception()
        raise
    return {
        'id': address.id,
        'desc': address.desc,
    }


@bind_context('doctor/address/update', login_required=True)
def address_update(ctx, address_id, address_info):
    doctor = get_doctor_from_context(ctx)
    try:
        address = DoctorAddress.objects.get(id=address_id, doctor_id=doctor.id)
    except DoctorAddress.DoesNotExist:
        logging_exception()
        raise
    for k, v in address_info.iteritems():
        setattr(address, k, v)
    address.save()


@bind_context('doctor/reserve/create_template', login_required=True)
def create_template(ctx):
    """
        获取/创建 模板
        @since 1.5.0 目前医生只有一个模板
    """
    doctor = get_doctor_from_context(ctx)
    template = ScheduleTemplate.objects.filter(doctor_id=doctor.id).first()
    if not template:
        data = []
        week_names = [u'周一', u'周二', u'周三', u'周四', u'周五', u'周六', u'周日']
        # 检查生成默认地址
        insert_address_if_empty(doctor)
        default_address = DoctorAddress.objects.filter(doctor_id=doctor.id).first()

        weekday = 0
        while weekday < len(week_names):
            data.append({
                'id': weekday,  # 一个星期的第几天，从0开始
                'title': u'{}时间表'.format(week_names[weekday]),
                'am': {
                    'start_time': AM_BEGIN_TIME[:5],
                    'end_time': AM_END_TIME[:5],
                    'address_desc': default_address.desc,
                    'address_id': default_address.id,
                    'reservation_type': RESERVATION_TYPE.ALL if weekday < 5 else '',
                },
                'pm': {
                    'start_time': PM_BEGIN_TIME[:5],
                    'end_time': PM_END_TIME[:5],
                    'address_desc': default_address.desc,
                    'address_id': default_address.id,
                    'reservation_type': RESERVATION_TYPE.ALL if weekday < 5 else '',
                }
            })
            weekday += 1

        content = json.dumps(data)
        template = ScheduleTemplate.objects.create(doctor=doctor, content=content)
    return {
        'id': template.id,
        'title': template.title,
        'content': json.loads(template.content),
    }


@bind_context('doctor/reserve/schedule_template', login_required=True)
def get_schedule_template(ctx):
    """
        获取模板
    """
    doctor = get_doctor_from_context(ctx)
    template = ScheduleTemplate.objects.filter(doctor_id=doctor.id).first()
    if template:
        return {
            'id': template.id,
            'title': template.title,
            'content': json.loads(template.content),
        }
    else:
        return gen(CODES.DOCTOR_NO_SCHEDULE_TEMPLATE)


@bind_context('doctor/reserve/update_template', login_required=True)
def update_template(ctx, template_id=None, content=None):
    """
        修改 时间表模板
        content是一个json arr str
    """
    if template_id is None:
        return gen(CODES.PARAMS_INCOMPLETE)
    try:
        template = ScheduleTemplate.objects.get(id=template_id)
        if content:
            template.content = content
            template.save()
    except ScheduleTemplate.DoesNotExist:
        logging_exception()
        raise


@bind_context('doctor/reserve/get', login_required=True)
def reserve_detail(ctx, id):
    """
        预约详情
    """
    try:
        reserve = Reservation.objects.get(id=id)
    except Reservation.DoesNotExist:
        logging_exception()
        return gen(CODES.RESERVATION_NOT_FOUND)

    order_info = reserve.order.data()
    return {
        'date': get_timestamp(reserve.date),
        'am_or_pm': reserve.schedule.am_or_pm,
        'reservation_address': reserve.schedule.address.desc,
        'reservation_type': reserve.reservation_type,
        'status': reserve.status,
        'order': order_info,
    }
