# Copyright 2018 Dario Lodeiros
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import logging
from datetime import datetime, timedelta
from odoo.exceptions import ValidationError
from odoo.tools import (
    DEFAULT_SERVER_DATE_FORMAT,
    DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
import odoo.addons.decimal_precision as dp
_logger = logging.getLogger(__name__)


class FolioWizard(models.TransientModel):
    _name = 'hotel.folio.wizard'

    @api.model
    def _get_default_center_user(self):
        user = self.env['res.users'].browse(self.env.uid)
        return user.has_group('hotel.group_hotel_call')

    @api.model
    def _get_default_checkin(self):
        folio = False
        if 'folio_id' in self._context:
            folio = self.env['hotel.folio'].search([
                ('id', '=', self._context['folio_id'])
            ])
        if folio and folio.room_lines:
            return folio.room_lines[0].checkin
        return fields.Date.today()

    @api.model
    def _get_default_checkout(self):
        folio = False
        if 'folio_id' in self._context:
            folio = self.env['hotel.folio'].search([
                ('id', '=', self._context['folio_id'])
            ])
        if folio and folio.room_lines:
            return folio.room_lines[0].checkout
        return fields.Date.today()

    @api.model
    def _get_default_channel_type(self):
        user = self.env['res.users'].browse(self.env.uid)
        if user.has_group('hotel.group_hotel_call'):
            return 'phone'

    @api.model
    def _get_default_pricelist(self):
        return self.env.user.hotel_id.default_pricelist_id.id

    partner_id = fields.Many2one('res.partner', required=True, string="Customer")
    email = fields.Char('E-mail')
    mobile = fields.Char('Mobile')

    pricelist_id = fields.Many2one('product.pricelist',
                                   string='Pricelist',
                                   required=True,
                                   default=_get_default_pricelist,
                                   help="Pricelist for current folio.")
    checkin = fields.Date('Check In', required=True,
                          default=_get_default_checkin)
    checkout = fields.Date('Check Out', required=True,
                           default=_get_default_checkout)
    credit_card_details = fields.Text('Credit Card Details')
    internal_comment = fields.Text(string='Internal Folio Notes')
    reservation_wizard_ids = fields.One2many('hotel.reservation.wizard',
                                             'folio_wizard_id',
                                             string="Resevations")
    service_wizard_ids = fields.One2many('hotel.service.wizard',
                                         'folio_wizard_id',
                                         string='Services')
    total = fields.Float('Total', compute='_computed_total')
    date_order = fields.Date('Date Order', default=fields.Datetime.now)
    confirm = fields.Boolean('Confirm Reservations', default="1")
    autoassign = fields.Boolean('Autoassign', default="1")
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env['res.company']._company_default_get('hotel.folio.wizard'))
    channel_type = fields.Selection([
        ('door', 'Door'),
        ('mail', 'Mail'),
        ('phone', 'Phone'),
        ('call', 'Call')
    ], string='Sales Channel', default=_get_default_channel_type)
    room_type_wizard_ids = fields.One2many('hotel.room.type.wizard',
                                           'folio_wizard_id',
                                           string="Room Types")
    call_center = fields.Boolean(default=_get_default_center_user)

    def assign_rooms(self):
        self.assign = True

    @api.onchange('pricelist_id')
    def onchange_pricelist_id(self):
        if self.pricelist_id:
            self.onchange_checks()

    @api.onchange('partner_id')
    def onchange_partner_id(self):
        if self.partner_id:
            vals = {}
            pricelist = self.partner_id.property_product_pricelist and \
                        self.partner_id.property_product_pricelist.id or \
                        self.env.user.hotel_id.default_pricelist_id.id
            vals.update({
                'pricelist_id': pricelist,
                'email': self.partner_id.email,
                'mobile': self.partner_id.mobile
                })
            self.update(vals)

    @api.onchange('autoassign')
    def create_reservations(self):
        self.ensure_one()
        cmds = [(5,0,0)]
        for line in self.room_type_wizard_ids:
            if line.rooms_num == 0:
                continue
            if line.rooms_num > line.max_rooms:
                raise ValidationError(_("Too many rooms!"))
            elif line.room_type_id:
                occupied = self.env['hotel.reservation'].get_reservations(
                    line.checkin,
                    (fields.Date.from_string(line.checkout) - timedelta(days=1)).
                        strftime(DEFAULT_SERVER_DATE_FORMAT))
                rooms_occupied = occupied.mapped('room_id.id')
                free_rooms = self.env['hotel.room'].search([
                    ('id', 'not in', rooms_occupied),
                    ('room_type_id.id', '=', line.room_type_id.id)
                ], order='sequence', limit=line.rooms_num)
                room_ids = free_rooms.mapped('id')
                room_list = self.env['hotel.room'].search([
                    ('id', 'in', room_ids)
                ])
                checkin_dt = fields.Date.from_string(line.checkin)
                checkout_dt = fields.Date.from_string(line.checkout)
                nights = abs((checkout_dt - checkin_dt).days)
                for room in room_list:
                    adults = self.env['hotel.room'].search([
                        ('id', '=', room.id)
                    ]).capacity
                    cmds.append((0, False, {
                        'checkin': line.checkin,
                        'checkout': line.checkout,
                        'discount': line.discount,
                        'room_id': room.id,
                        'nights': nights,
                        'adults': adults,
                        'folio_wizard_id': self.id,
                        'board_service_room_id': line.board_service_room_id,
                        'children': 0,
                        'room_type_id': line.room_type_id,
                        'price': line.price,
                    }))
        self.reservation_wizard_ids = cmds

    @api.multi
    @api.onchange('checkin', 'checkout')
    def onchange_checks(self):
        '''
        When you change checkin or checkout it will checked it
        and update the qty of hotel folio line
        -----------------------------------------------------------------
        @param self: object pointer
        '''
        self.ensure_one()
        tz_hotel = self.env.user.hotel_id.tz
        today = fields.Date.context_today(self.with_context(tz=tz_hotel))
        checkin_dt = fields.Date.from_string(today) if not self.checkin else fields.Date.from_string(self.checkin)
        checkout_dt = fields.Date.from_string(today) if not self.checkout else fields.Date.from_string(self.checkout)
        if checkin_dt >= checkout_dt:
            checkout_dt = checkin_dt + timedelta(days=1)

        checkin_str = checkin_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
        checkout_str = checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)

        cmds = [(5,0,0)]
        room_type_ids = self.env['hotel.room.type'].search([])
        for room_type in room_type_ids:
            cmds.append((0, False, {
                'room_type_id': room_type.id,
                'folio_wizard_id': self.id,
                'checkin': checkin_str,
                'checkout': checkout_str,
                }))
        self.update({
            'checkin': checkin_str,
            'checkout': checkout_str,
            'room_type_wizard_ids': cmds,
        })
        for room_type in self.room_type_wizard_ids:
            room_type.update_price()

    @api.depends('room_type_wizard_ids.total_price',
                 'reservation_wizard_ids.price',
                 'reservation_wizard_ids',
                 'service_wizard_ids.price_total')
    def _computed_total(self):
        total = 0
        for line in self.service_wizard_ids:
            total += line.price_total

        if not self.reservation_wizard_ids:
            for line in self.room_type_wizard_ids:
                total += line.total_price
        else:
            for line in self.reservation_wizard_ids:
                total += line.price
        self.total = total

    @api.multi
    def create_folio(self):
        self.ensure_one()
        if not self.partner_id:
            raise ValidationError(_("We need know the customer!"))
        reservations = [(5, False, False)]
        services = []
        if self.autoassign:
            self.create_reservations()
        for line in self.reservation_wizard_ids:
            services_room = []
            for product in line.product_ids:
                services_room.append((0, False, {
                    'product_id': product.id
                }))
            reservations.append((0, False, {
                'room_id': line.room_id.id,
                'adults': line.adults,
                'children': line.children,
                'checkin': line.checkin,
                'checkout': line.checkout,
                'discount': line.discount,
                'room_type_id': line.room_type_id.id,
                'board_service_room_id': line.board_service_room_id.id,
                'to_assign': line.to_assign,
                'service_ids': services_room,
                'pricelist_id': self.pricelist_id.id, # REVIEW: Create folio with reservations dont respect the pricelist_id on folio dict
            }))
        for line in self.service_wizard_ids:
            services.append((0, False, {
                'product_id': line.product_id.id,
                'discount': line.discount,
                'price_unit': line.price_unit,
                'product_qty': line.product_uom_qty,
            }))
        if not self.reservation_wizard_ids:
            raise ValidationError(_('We cant create avoid folio'))
        vals = {
            'partner_id': self.partner_id.id,
            'channel_type': self.channel_type,
            'room_lines': reservations,
            'service_ids': services,
            'pricelist_id': self.pricelist_id.id,
            'internal_comment': self.internal_comment,
            'credit_card_details': self.credit_card_details,
            'email': self.email,
            'mobile': self.mobile,
        }
        newfol = self.env['hotel.folio'].create(vals)
        if self.confirm:
            newfol.room_lines.confirm()
        action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0]
        if newfol:
            action['views'] = [(self.env.ref('hotel.hotel_folio_view_form').id, 'form')]
            action['res_id'] = newfol.id
        else:
            action = {'type': 'ir.actions.act_window_close'}
        return action


class HotelRoomTypeWizards(models.TransientModel):
    _name = 'hotel.room.type.wizard'

    @api.multi
    def _get_default_checkin(self):
        return self.folio_wizard_id.checkin

    @api.model
    def _get_default_checkout(self):
        return self.folio_wizard_id.checkout

    room_type_id = fields.Many2one('hotel.room.type',
                                   string="Rooms Type")
    rooms_num = fields.Integer('Number of Rooms')
    max_rooms = fields.Integer('Max', compute="_compute_max")
    price = fields.Float(string='Price by Room')
    total_price = fields.Float(string='Total Price',
                               compute="_compute_total",
                               store="True")
    folio_wizard_id = fields.Many2one('hotel.folio.wizard')
    discount = fields.Float('discount')
    min_stay = fields.Integer('Min. Days', compute="_compute_max")
    checkin = fields.Date('Check In', required=True,
                              default=_get_default_checkin)
    checkout = fields.Date('Check Out', required=True,
                               default=_get_default_checkout)
    can_confirm = fields.Boolean(compute="_can_confirm")
    board_service_room_id = fields.Many2one('hotel.board.service.room.type',
                                            string="Board Service")

    @api.multi
    @api.onchange('rooms_num')
    def domain_board_service(self):
        for line in self:
            board_service_room_ids = []
            if line.room_type_id:
                pricelist_id = self.folio_wizard_id.pricelist_id.id
                board_services_room_type = self.env['hotel.board.service.room.type'].\
                    search([('hotel_room_type_id', '=', self.room_type_id.id),
                            ('pricelist_id', 'in', (pricelist_id, False))])
                board_service_room_ids = board_services_room_type.ids
            domain_boardservice = [('id', 'in', board_service_room_ids)]
            return {'domain': {'board_service_room_id': domain_boardservice}}

    @api.multi
    def _can_confirm(self):
        for record in self:
            date_start = fields.Date.from_string(record.checkin)
            date_end = fields.Date.from_string(record.checkout)
            date_diff = abs((date_end - date_start).days)
            record.can_confirm = record.max_rooms > 0 and record.min_stay <= date_diff

    def _compute_max(self):
        # REVIEW: This methid has a incorrect dependencies with hotel_channel_conector
        # because use avail model defined on this module
        for res in self:
            user = self.env['res.users'].browse(self.env.uid)
            date_start = fields.Date.from_string(res.checkin)
            date_end = fields.Date.from_string(res.checkout)
            date_diff = abs((date_end - date_start).days)
            minstay_restrictions = self.env['hotel.room.type.restriction.item'].search([
                ('room_type_id', '=', res.room_type_id.id),
            ])
            avail_restrictions = self.env['hotel.room.type.availability'].search([
                ('room_type_id', '=', res.room_type_id.id)
            ])
            real_max = len(res.room_type_id.check_availability_room_type(
                        res.checkin,
                        (fields.Date.from_string(res.checkout) -
                            timedelta(days=1)).strftime(
                                DEFAULT_SERVER_DATE_FORMAT
                                ),
                        res.room_type_id.id))
            res.real_avail = real_max
            avail = 100000
            min_stay = 0
            dates = []
            for i in range(0, date_diff):
                ndate_dt = date_start + timedelta(days=i)
                ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
                dates.append(ndate_str)
                if minstay_restrictions:
                    date_min_days = minstay_restrictions.filtered(
                                lambda r: r.date == ndate_str).min_stay
                    if date_min_days > min_stay:
                        min_stay = date_min_days
                if user.has_group('hotel.group_hotel_call'):
                    max_avail = real_max
                    restriction = False
                    if avail_restrictions:
                        restriction = avail_restrictions.filtered(
                                    lambda r: r.date == ndate_str)
                        if restriction:
                            if restriction.channel_bind_ids[0]:
                                max_avail = restriction.channel_bind_ids[0].channel_avail
                    if not restriction and res.room_type_id.channel_bind_ids:
                        if res.room_type_id.channel_bind_ids[0]:
                            max_avail = res.room_type_id.channel_bind_ids[0].default_availability
                    if max_avail < avail:
                        avail = min(max_avail, real_max)
                else:
                    avail = real_max

            if avail < 100000 and avail > 0:
                res.max_rooms = avail
            else:
                res.max_rooms = 0
            if min_stay > 0:
                res.min_stay = min_stay

    @api.depends('rooms_num', 'price')
    def _compute_total(self):
        for res in self:
            res.total_price = res.price * res.rooms_num

    @api.onchange('rooms_num', 'discount', 'price', 'board_service_room_id',
                  'room_type_id', 'checkin', 'checkout')
    def update_price(self):
        for record in self:
            if record.rooms_num > record.max_rooms:
                raise ValidationError(_("There are not enough rooms!"))
            checkin = record.checkin or record.folio_wizard_id.checkin
            checkout = record.checkout or record.folio_wizard_id.checkout
            chkin_utc_dt = fields.Date.from_string(checkin)
            chkout_utc_dt = fields.Date.from_string(checkout)
            if chkin_utc_dt >= chkout_utc_dt:
                chkout_utc_dt = chkin_utc_dt + timedelta(days=1)
            nights = abs((chkout_utc_dt - chkin_utc_dt).days)
            pricelist_id = self.folio_wizard_id.pricelist_id.id
            res_price = 0
            for i in range(0, nights):
                ndate = chkin_utc_dt + timedelta(days=i)
                ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
                product = record.room_type_id.product_id.with_context(
                    lang=record.folio_wizard_id.partner_id.lang,
                    partner=record.folio_wizard_id.partner_id.id,
                    quantity=1,
                    date=ndate_str,
                    pricelist=pricelist_id,
                    uom=record.room_type_id.product_id.uom_id.id)
                res_price += product.price

            board_service = record.board_service_room_id
            if board_service:
                if board_service.price_type == 'fixed':
                    board_price = board_service.amount *\
                        record.room_type_id.capacity * nights
                    res_price += board_price
                else:
                    res_price += ((res_price * board_service.amount) * 0.01)
            price = res_price - ((res_price * record.discount) * 0.01)
            vals = {
                'checkin': checkin,
                'checkout': checkout,
                'price': price,
            }
            record.update(vals)


class ReservationWizard(models.TransientModel):
    _name = 'hotel.reservation.wizard'
    _rec_name = 'room_id'

    room_id = fields.Many2one('hotel.room',
                              string="Room")
    folio_wizard_id = fields.Many2one('hotel.folio.wizard')
    adults = fields.Integer('Adults',
                            help='List of adults there in guest list. ')
    children = fields.Integer('Children',
                              help='Number of children there in guest list.')
    checkin = fields.Date('Check In', required=True)
    checkout = fields.Date('Check Out', required=True)
    room_type_id = fields.Many2one('hotel.room.type',
                                   string='Room Type',
                                   required=True)
    nights = fields.Integer('Nights', readonly=True)
    price = fields.Float(string='Total')
    partner_id = fields.Many2one(related='folio_wizard_id.partner_id')
    discount = fields.Float('discount')
    to_assign = fields.Boolean(compute="_compute_assign")
    product_ids = fields.Many2many('product.product',
                                   string="Products")
    board_service_room_id = fields.Many2one('hotel.board.service.room.type',
                                            string="Board Service")

    @api.multi
    def _compute_assign(self):
        for rec in self:
            user = self.env['res.users'].browse(self.env.uid)
            if user.has_group('hotel.group_hotel_call'):
                rec.to_assign = True

    @api.multi
    @api.onchange('room_id')
    def onchange_room_id(self):
        for line in self:
            if line.checkin and line.checkout:
                if line.adults == 0:
                    line.adults = line.room_id.capacity
                line.room_type_id = line.room_id.room_type_id.id
                checkout_dt = fields.Date.from_string(line.checkout)
                checkout_dt -= timedelta(days=1)
                occupied = self.env['hotel.reservation'].get_reservations(
                    line.checkin,
                    checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
                rooms_occupied = occupied.mapped('room_id.id')
                if line.room_id.id in rooms_occupied:
                    raise ValidationError(_("This room is occupied!, please, \
                        choice other room or change the reservation date"))

    @api.multi
    @api.onchange('checkin', 'checkout', 'room_type_id',
                  'discount', 'board_service_room_id', 'product_ids')
    def onchange_dates(self):
        for line in self:
            if not line.checkin:
                line.checkin = line.folio_wizard_id.checkin
            if not line.checkout:
                line.checkout = line.folio_wizard_id.checkout

            start_date_utc_dt = fields.Date.from_string(line.checkin)
            end_date_utc_dt = fields.Date.from_string(line.checkout)

            if line.room_type_id:
                #First, compute room price
                pricelist_id = line.folio_wizard_id.pricelist_id.id
                nights = abs((end_date_utc_dt - start_date_utc_dt).days)
                res_price = 0
                for i in range(0, nights):
                    ndate = start_date_utc_dt + timedelta(days=i)
                    ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
                    product = line.room_type_id.product_id.with_context(
                        lang=line.partner_id.lang,
                        partner=line.partner_id.id,
                        quantity=1,
                        date=ndate_str,
                        pricelist=pricelist_id,
                        uom=line.room_type_id.product_id.uom_id.id)
                    res_price += product.price
                board_service = line.board_service_room_id
                #Second, compute BoardService price
                if board_service:
                    if board_service.price_type == 'fixed':
                        board_price = board_service.amount *\
                            line.adults * nights
                        res_price += board_price
                    else:
                        res_price += ((res_price * board_service.amount) * 0.01)
                res_price = res_price - (res_price * line.discount) * 0.01
                #And compute products Room price
                for product in line.product_ids:
                    pricelist_id = line.folio_wizard_id.pricelist_id.id
                    product = product.with_context(
                        lang=line.folio_wizard_id.partner_id.lang,
                        partner=line.folio_wizard_id.partner_id.id,
                        quantity=1,
                        date=fields.Datetime.now(),
                        pricelist=pricelist_id,
                        uom=product.uom_id.id)
                    values = {'pricelist_id': pricelist_id,
                              'product_id': product.id,
                              'product_qty': 1}
                    service_line = self.env['hotel.service'].new(values)
                    vals = service_line.with_context({
                            'default_folio_id': line.folio_wizard_id
                            })._prepare_add_missing_fields(values)
                    if product.per_day or product.per_person:
                        checkin_dt = fields.Date.from_string(line.checkin)
                        checkout_dt = fields.Date.from_string(line.checkout)
                        nights = abs((checkout_dt - checkin_dt).days)
                        vals.update(service_line.prepare_service_lines(
                                dfrom=line.checkin,
                                days=nights,
                                per_person=product.per_person,
                                persons=line.adults,
                                old_line_days=False,
                                consumed_on=product.consumed_on,))
                    service_line.update(vals)
                    price_product = service_line.price_unit * (1 - (line.discount or 0.0) * 0.01)
                    pricelist = line.folio_wizard_id.pricelist_id
                    currency = pricelist.currency_id
                    taxes = service_line.tax_ids.compute_all(
                        price_product,
                        currency,
                        service_line.product_qty,
                        product=product
                        )
                    # TODO Daily Limits
                    res_price += taxes['total_included']
                line.price = res_price
            end_date_utc_dt -= timedelta(days=1)
            occupied = self.env['hotel.reservation'].get_reservations(
                line.checkin,
                end_date_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
            rooms_occupied = occupied.mapped('room_id.id')
            domain_rooms = [('id', 'not in', rooms_occupied)]
            return {'domain': {'room_id': domain_rooms}}


class ServiceWizard(models.TransientModel):
    _name = 'hotel.service.wizard'

    product_id = fields.Many2one('product.product',
                                 string="Service")
    name = fields.Char('Description')
    folio_wizard_id = fields.Many2one('hotel.folio.wizard')
    discount = fields.Float('discount')
    price_unit = fields.Float('Unit Price', required=True,
                              digits=dp.get_precision('Product Price'),
                              default=0.0)
    price_total = fields.Float(compute='_compute_amount', string='Total',
                               readonly=True, store=True)
    tax_ids = fields.Many2many('account.tax', string='Taxes',
                               domain=['|', ('active', '=', False),
                                       ('active', '=', True)])
    product_uom_qty = fields.Float(string='Quantity',
                                   digits=dp.get_precision('Product Unit of Measure'),
                                   required=True,
                                   default=1.0)

    @api.onchange('product_id')
    def onchange_product_id(self):
        if self.product_id:
            pricelist_id = self.folio_wizard_id.pricelist_id.id
            prod = self.product_id.with_context(
                lang=self.folio_wizard_id.partner_id.lang,
                partner=self.folio_wizard_id.partner_id.id,
                quantity=1,
                date=fields.Datetime.now(),
                pricelist=pricelist_id,
                uom=self.product_id.uom_id.id)
            # TODO change pricelist for partner
            values = {'pricelist_id': pricelist_id, 'product_id': prod.id}
            line = self.env['hotel.service'].new(values)
            vals = line.with_context({
                    'default_folio_id': self.folio_wizard_id
                    })._prepare_add_missing_fields(values)
            self.update(vals)

    @api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_ids')
    def _compute_amount(self):
        """
        Compute the amounts of the service line.
        """
        for record in self:
            pricelist = record.folio_wizard_id.pricelist_id
            currency = pricelist.currency_id
            product = record.product_id
            price = record.price_unit * (1 - (record.discount or 0.0) * 0.01)
            taxes = record.tax_ids.compute_all(price,
                                               currency,
                                               record.product_uom_qty,
                                               product=product)
            record.update({
                'price_total': taxes['total_included'],
            })
