import datetime

from odoo import _, api, fields, models


class BookingEngine(models.TransientModel):
    _name = "pms.booking.engine"
    _description = "Booking engine"
    _check_pms_properties_auto = True

    start_date = fields.Date(
        string="From:",
        help="Start date for creation of reservations and folios",
        required=True,
    )
    end_date = fields.Date(
        string="To:",
        help="End date for creation of reservations and folios",
        required=True,
    )
    pricelist_id = fields.Many2one(
        string="Pricelist",
        help="Pricelist applied in folio",
        readonly=False,
        store=True,
        comodel_name="product.pricelist",
        compute="_compute_pricelist_id",
        check_pms_properties=True,
        domain="[('is_pms_available', '=', True)]",
    )
    pms_property_id = fields.Many2one(
        string="Property",
        help="Property to which the folio belongs",
        default=lambda self: self._default_pms_property_id(),
        comodel_name="pms.property",
        check_pms_properties=True,
    )
    segmentation_ids = fields.Many2many(
        string="Segmentation",
        help="Partner Tags",
        ondelete="restrict",
        comodel_name="res.partner.category",
        domain="[('is_used_in_checkin', '=', True)]",
    )
    partner_name = fields.Char(
        string="Partner name",
        help="In whose name is the reservation",
        compute="_compute_partner_name",
        readonly=False,
        store=True,
    )
    partner_id = fields.Many2one(
        string="Partner",
        help="Partner who made the reservation",
        comodel_name="res.partner",
        compute="_compute_partner_id",
        readonly=False,
        store=True,
        check_pms_properties=True,
    )
    folio_id = fields.Many2one(
        string="Folio",
        help="Folio in which are included new reservations",
        comodel_name="pms.folio",
        check_pms_properties=True,
    )
    availability_results = fields.One2many(
        string="Availability Results",
        help="Availability Results",
        readonly=False,
        store=True,
        comodel_name="pms.folio.availability.wizard",
        inverse_name="booking_engine_id",
        compute="_compute_availability_results",
        check_pms_properties=True,
    )
    reservation_type = fields.Selection(
        string="Type",
        help="The type of the reservation. "
        "Can be 'Normal', 'Staff' or 'Out of Service'",
        default=lambda *a: "normal",
        selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")],
    )
    agency_id = fields.Many2one(
        string="Agency",
        help="Agency that made the reservation",
        comodel_name="res.partner",
        domain=[("is_agency", "=", True)],
        ondelete="restrict",
    )
    channel_type_id = fields.Many2one(
        string="Direct Sale Channel",
        help="Sales Channel through which the reservation was managed",
        readonly=False,
        store=True,
        comodel_name="pms.sale.channel",
        domain=[("channel_type", "=", "direct")],
        ondelete="restrict",
        compute="_compute_channel_type_id",
        required=True,
    )
    total_price_folio = fields.Float(
        string="Total Price",
        help="Total price of folio with taxes",
        compute="_compute_total_price_folio",
    )
    discount = fields.Float(
        string="Discount",
        help="Discount that be applied in total price",
        default=0,
    )
    can_create_folio = fields.Boolean(
        string="Can create folio", compute="_compute_can_create_folio"
    )
    internal_comment = fields.Text(
        string="Internal Folio Notes",
        help="Internal Folio notes for Staff",
    )

    def _default_pms_property_id(self):
        if self._context.get("default_folio_id"):
            folio = self.env["pms.folio"].browse(self._context.get("default_folio_id"))
            return folio.pms_property_id.id
        else:
            return self.env.user.get_active_property_ids()[0]

    @api.depends("availability_results.value_num_rooms_selected")
    def _compute_can_create_folio(self):
        for record in self:
            record.can_create_folio = any(
                record.availability_results.mapped("value_num_rooms_selected")
            )

    @api.depends("partner_id")
    def _compute_pricelist_id(self):
        for record in self:
            record.pricelist_id = (
                record.partner_id.property_product_pricelist.id
                if record.partner_id.property_product_pricelist.is_pms_available
                else self.pms_property_id.default_pricelist_id.id
            )

    @api.depends("agency_id")
    def _compute_channel_type_id(self):
        for record in self:
            if record.agency_id:
                record.channel_type_id = record.agency_id.sale_channel_id.id

    @api.depends("availability_results.price_total", "discount")
    def _compute_total_price_folio(self):
        for record in self:
            record.total_price_folio = 0
            for line in record.availability_results:
                record.total_price_folio += line.price_total
            record.total_price_folio = record.total_price_folio * (1 - record.discount)

    @api.depends("agency_id")
    def _compute_partner_id(self):
        for record in self:
            if record.agency_id and record.agency_id.invoice_to_agency == "always":
                record.partner_id = record.agency_id.id
            elif not record.partner_id:
                record.partner_id = False

    @api.depends("partner_id", "agency_id")
    def _compute_partner_name(self):
        for record in self:
            if record.partner_id:
                record.partner_name = record.partner_id.name
            if (
                record.agency_id
                and not record.agency_id.invoice_to_agency == "always"
                and not record.partner_name
            ):
                record.partner_name = _("Reservation from ") + record.agency_id.name
            elif not record.partner_name:
                record.partner_name = False

    @api.depends(
        "start_date",
        "end_date",
        "pricelist_id",
    )
    def _compute_availability_results(self):
        for record in self:
            record.availability_results = False

            if record.start_date and record.end_date:
                if record.end_date == record.start_date:
                    record.end_date = record.end_date + datetime.timedelta(days=1)

                cmds = [(5, 0, 0)]

                for room_type_iterator in self.env["pms.room.type"].search(
                    [
                        "|",
                        ("pms_property_ids", "=", False),
                        ("pms_property_ids", "in", record.pms_property_id.id),
                    ]
                ):
                    pms_property = record.pms_property_id
                    pms_property = pms_property.with_context(
                        checkin=record.start_date,
                        checkout=record.end_date,
                        room_type_id=room_type_iterator.id,
                        pricelist_id=record.pricelist_id.id,
                    )
                    num_rooms_available = pms_property.availability

                    cmds.append(
                        (
                            0,
                            0,
                            {
                                "booking_engine_id": record.id,
                                "checkin": record.start_date,
                                "checkout": record.end_date,
                                "room_type_id": room_type_iterator.id,
                                "num_rooms_available": num_rooms_available,
                            },
                        )
                    )
                    # remove old items
                    old_lines = record.availability_results.mapped("id")
                    for old_line in old_lines:
                        cmds.append((2, old_line))

                    record.availability_results = cmds

                    record.availability_results = record.availability_results.sorted(
                        key=lambda s: s.num_rooms_available, reverse=True
                    )

    def create_folio(self):
        for record in self:
            if not record.folio_id:
                folio = self.env["pms.folio"].create(
                    {
                        "reservation_type": record.reservation_type,
                        "pricelist_id": record.pricelist_id.id,
                        "partner_id": record.partner_id.id
                        if record.partner_id
                        else False,
                        "partner_name": record.partner_name,
                        "pms_property_id": record.pms_property_id.id,
                        "agency_id": record.agency_id.id,
                        "sale_channel_origin_id": record.channel_type_id.id,
                        "segmentation_ids": [(6, 0, record.segmentation_ids.ids)],
                        "internal_comment": record.internal_comment,
                    }
                )
            else:
                folio = record.folio_id
            reservation_values = []
            for line in record.availability_results:
                for _reservations_to_create in range(0, line.value_num_rooms_selected):
                    res_dict = {
                        "folio_id": folio.id,
                        "checkin": line.checkin,
                        "checkout": line.checkout,
                        "room_type_id": line.room_type_id.id,
                        "partner_id": record.partner_id.id
                        if record.partner_id
                        else False,
                        "partner_name": record.partner_name,
                        "pricelist_id": record.pricelist_id.id,
                        "pms_property_id": folio.pms_property_id.id,
                        "board_service_room_id": line.board_service_room_id.id,
                    }
                    reservation_values.append((0, 0, res_dict))
            folio.write(
                {
                    "reservation_ids": reservation_values,
                }
            )
            if record.discount:
                # TODO: Refact compute discount in reservation and service lines
                folio.reservation_ids.reservation_line_ids.discount = (
                    record.discount * 100
                )
            action = self.sudo().env.ref("pms.open_pms_folio1_form_tree_all").read()[0]
            action["views"] = [
                (self.sudo().env.ref("pms.pms_folio_view_form").id, "form")
            ]
            action["res_id"] = folio.id
            return action


class NumRoomsSelectionModel(models.TransientModel):
    _name = "pms.num.rooms.selection"
    _description = "Dinamic Selection based on avails room"
    _rec_name = "value"

    value = fields.Integer()
    room_type_id = fields.Char()
    booking_engine_id = fields.One2many(
        comodel_name="pms.folio.availability.wizard",
        inverse_name="num_rooms_selected",
    )


class AvailabilityWizard(models.TransientModel):
    _name = "pms.folio.availability.wizard"
    _description = "Room type line in Booking Engine"
    _check_pms_properties_auto = True

    booking_engine_id = fields.Many2one(
        string="Folio Wizard ID",
        comodel_name="pms.booking.engine",
    )
    checkin = fields.Date(
        string="From:",
        help="Date Reservation starts ",
        required=True,
    )
    checkout = fields.Date(
        string="To:",
        help="Date Reservation ends",
        required=True,
    )
    room_type_id = fields.Many2one(
        string="Room Type",
        help="Room Type reserved",
        comodel_name="pms.room.type",
        check_pms_properties=True,
    )

    num_rooms_available = fields.Integer(
        string="Available rooms",
        help="Number of rooms that are available",
        store=True,
        compute="_compute_num_rooms_available",
    )
    num_rooms_selected = fields.Many2one(
        string="Selected rooms",
        readonly=False,
        store=True,
        comodel_name="pms.num.rooms.selection",
        domain="[('value', '<=', num_rooms_available), "
        "('room_type_id', '=', room_type_id)]",
        compute="_compute_num_rooms_selected",
    )
    value_num_rooms_selected = fields.Integer(
        string="Number of Rooms Selected",
        readonly=False,
        store=True,
        compute="_compute_value_num_rooms_selected",
    )
    price_per_room = fields.Float(
        string="Price per room",
        help="Price per room in folio",
        compute="_compute_price_per_room",
    )
    price_total = fields.Float(
        string="Total price",
        help="The total price in the folio",
        compute="_compute_price_total",
    )
    pms_property_id = fields.Many2one(
        string="Property",
        help="Propertiy with access to the element;",
        related="booking_engine_id.pms_property_id",
    )
    board_service_room_id = fields.Many2one(
        string="Board Service",
        help="Board Service included in the room",
        comodel_name="pms.board.service.room.type",
        domain="""
            [
                ('pms_room_type_id','=',room_type_id),
                ('pms_property_id','=',pms_property_id)
            ]
        """,
        check_pms_properties=True,
    )

    @api.depends("room_type_id", "checkin", "checkout")
    def _compute_num_rooms_available(self):
        for record in self:
            pms_property = record.booking_engine_id.pms_property_id
            pms_property = pms_property.with_context(
                checkin=record.checkin,
                checkout=record.checkout,
                room_type_id=record.room_type_id.id,
                pricelist_id=record.booking_engine_id.pricelist_id.id,
            )
            record.num_rooms_available = pms_property.availability

    @api.depends("num_rooms_available")
    def _compute_num_rooms_selected(self):
        for record in self:
            for elem_to_insert in range(0, record.num_rooms_available + 1):
                if (
                    self.env["pms.num.rooms.selection"].search_count(
                        [
                            ("value", "=", elem_to_insert),
                            ("room_type_id", "=", record.room_type_id.id),
                        ]
                    )
                    == 0
                ):
                    self.env["pms.num.rooms.selection"].create(
                        {
                            "value": elem_to_insert,
                            "room_type_id": record.room_type_id.id,
                        }
                    )
            record.num_rooms_selected = self.env["pms.num.rooms.selection"].search(
                [("value", "=", 0), ("room_type_id", "=", record.room_type_id.id)]
            )

    @api.depends("num_rooms_selected")
    def _compute_value_num_rooms_selected(self):
        for record in self:
            if record.num_rooms_selected:
                record.value_num_rooms_selected = record.num_rooms_selected.value
            elif not record.value_num_rooms_selected:
                record.value_num_rooms_selected = 0

    @api.depends("room_type_id", "board_service_room_id", "checkin", "checkout")
    def _compute_price_per_room(self):
        for record in self:
            record.price_per_room = self._get_price_by_room_type(
                room_type_id=record.room_type_id.id,
                board_service_room_id=record.board_service_room_id.id,
                checkin=record.checkin,
                checkout=record.checkout,
                pricelist_id=record.booking_engine_id.pricelist_id.id,
                pms_property_id=record.booking_engine_id.pms_property_id.id,
            )

    @api.depends("price_per_room", "value_num_rooms_selected")
    def _compute_price_total(self):
        for record in self:
            record.price_total = record.price_per_room * record.value_num_rooms_selected

    @api.model
    def _get_price_by_room_type(
        self,
        room_type_id,
        checkin,
        checkout,
        board_service_room_id,
        pricelist_id,
        pms_property_id,
        adults=False,
    ):
        room_type_total_price_per_room = 0
        room_type = self.env["pms.room.type"].browse(room_type_id)
        pms_property = self.env["pms.property"].browse(pms_property_id)
        for date_iterator in [
            checkin + datetime.timedelta(days=x)
            for x in range(0, (checkout - checkin).days)
        ]:
            product = room_type.product_id
            product = product.with_company(pms_property.company_id).with_context(
                quantity=1,
                date=fields.Date.today(),
                consumption_date=date_iterator,
                pricelist=pricelist_id,
                uom=product.uom_id.id,
                property=pms_property_id,
            )
            room_type_total_price_per_room += product.price

        if board_service_room_id:
            board_service_room = self.env["pms.board.service.room.type"].browse(
                board_service_room_id
            )
            nights = (checkout - checkin).days
            adults = adults or room_type.get_room_type_capacity(pms_property_id)
            room_type_total_price_per_room += (
                board_service_room.amount * nights * adults
            )
        return room_type_total_price_per_room
