import datetime

from odoo import _, api, fields, models
from odoo.exceptions import UserError


class ReservationSplitJoinSwapWizard(models.TransientModel):
    _name = "pms.reservation.split.join.swap.wizard"
    _description = "Operations in reservations"

    operation = fields.Selection(
        string="Operation",
        help="Operation to be applied on the reservation",
        selection=[
            ("swap", "Swap rooms"),
            ("split", "Split reservation"),
            ("join", "Join reservation"),
        ],
        default=lambda self: self._context.get("default_operation")
        if self._context.get("default_operation")
        else "swap",
    )
    reservation_id = fields.Many2one(
        string="Reservation",
        default=lambda self: self.env["pms.reservation"]
        .browse(self._context.get("active_id"))
        .id
        if self._context.get("active_id")
        else False,
        comodel_name="pms.reservation",
    )
    checkin = fields.Date(
        string="Check In",
        help="Checkin in reservation",
        default=lambda self: self.env["pms.reservation"]
        .browse(self._context.get("active_id"))
        .checkin
        if self._context.get("active_id")
        else False,
    )
    checkout = fields.Date(
        string="Check Out",
        help="checkout in reservation",
        default=lambda self: self.env["pms.reservation"]
        .browse(self._context.get("active_id"))
        .checkout
        if self._context.get("active_id")
        else False,
    )
    reservation_ids = fields.Many2many(
        string="Reservations",
        readonly=False,
        store=True,
        comodel_name="pms.reservation",
        compute="_compute_reservation_ids",
    )
    room_source = fields.Many2one(
        string="Room Source",
        default=lambda self: self.env["pms.reservation"]
        .browse(self._context.get("active_id"))
        .preferred_room_id
        if self._context.get("active_id")
        and not self.env["pms.reservation"]
        .browse(self._context.get("active_id"))
        .splitted
        else False,
        comodel_name="pms.room",
        domain="[('id', 'in', allowed_rooms_sources)]",
    )
    room_target = fields.Many2one(
        string="Room Target",
        comodel_name="pms.room",
        domain="[('id', 'in', allowed_rooms_target)]",
    )
    allowed_rooms_sources = fields.Many2many(
        string="Allowed rooms source",
        store=True,
        readonly=False,
        comodel_name="pms.room",
        relation="pms_wizard_split_join_swap_reservation_rooms_source",
        column1="wizard_id",
        column2="room_id",
        compute="_compute_allowed_rooms_source",
    )
    allowed_rooms_target = fields.Many2many(
        string="Allowed rooms target",
        comodel_name="pms.room",
        store=True,
        readonly=False,
        relation="pms_wizard_split_join_swap_reservation_rooms_target",
        column1="wizard_id",
        column2="room_id",
        compute="_compute_allowed_rooms_target",
    )
    reservation_lines_to_change = fields.One2many(
        string="Reservations Lines To Change",
        comodel_name="pms.wizard.reservation.lines.split",
        inverse_name="reservation_wizard_id",
        compute="_compute_reservation_lines",
        store=True,
        readonly=False,
    )

    @api.depends("checkin", "checkout", "room_source", "room_target")
    def _compute_reservation_ids(self):
        for record in self:
            if record.checkin and record.checkout:
                reservation_ids = list()
                for date_iterator in [
                    record.checkin + datetime.timedelta(days=x)
                    for x in range(0, (record.checkout - record.checkin).days)
                ]:
                    domain_lines = [
                        ("occupies_availability", "=", True),
                        ("overbooking", "=", False),
                        (
                            "pms_property_id",
                            "=",
                            record.reservation_id.pms_property_id.id,
                        ),
                    ]
                    if record.room_source and record.room_target:
                        domain_lines.extend(
                            [
                                "|",
                                ("room_id", "=", record.room_source.id),
                                ("room_id", "=", record.room_target.id),
                            ]
                        )

                    domain_lines.append(("date", "=", date_iterator))
                    lines = self.env["pms.reservation.line"].search(domain_lines)
                    reservation_ids.extend(lines.mapped("reservation_id.id"))
                reservations = (
                    self.env["pms.reservation"]
                    .search(
                        [
                            ("id", "in", reservation_ids),
                            ("rooms", "!=", False),
                        ],
                    )
                    .sorted("rooms")
                )
                record.reservation_ids = reservations
            else:
                record.reservation_ids = False

    @api.depends("reservation_id")
    def _compute_reservation_lines(self):
        for record in self:
            if record.reservation_id:
                cmds = [(5, 0, 0)]
                for line in record.reservation_id.reservation_line_ids:
                    cmds.append(
                        (
                            0,
                            0,
                            {
                                "reservation_wizard_id": record.id,
                                "room_id": line.room_id,
                                "date": line.date,
                            },
                        )
                    )
                    record.reservation_lines_to_change = cmds
            else:
                record.reservation_lines_to_change = False

    @api.depends("checkin", "checkout")
    def _compute_allowed_rooms_source(self):
        for record in self:
            record.allowed_rooms_sources = (
                record.reservation_ids.reservation_line_ids.mapped("room_id")
            )

    @api.depends_context("default_operation")
    @api.depends("checkin", "checkout", "room_source", "operation")
    def _compute_allowed_rooms_target(self):
        for record in self:
            record.allowed_rooms_target = False
            record.room_target = False
            if record.checkin and record.checkout:
                pms_property = record.reservation_id.pms_property_id
                pms_property = pms_property.with_context(
                    checkin=record.checkin,
                    checkout=record.checkout,
                    room_type_id=False,  # Allows to choose any available room
                    current_lines=record.reservation_id.reservation_line_ids.ids,
                    pricelist_id=record.reservation_id.pricelist_id.id,
                    real_avail=True,
                )
                rooms_available = pms_property.free_room_ids

                domain = [("capacity", ">=", record.reservation_id.adults)]
                if record.room_source:
                    domain.append(("id", "!=", record.room_source.id))

                if record.operation == "swap":
                    domain.append(
                        (
                            "id",
                            "in",
                            record.reservation_ids.reservation_line_ids.mapped(
                                "room_id"
                            ).ids,
                        )
                    )
                else:
                    domain.extend(
                        [
                            ("id", "in", rooms_available.ids),
                        ]
                    )
                rooms = self.env["pms.room"].search(domain)
                record.allowed_rooms_target = rooms

    @api.model
    def reservation_split(self, reservation, date, room):
        if not reservation:
            raise UserError(_("Invalid reservation"))
        if not reservation or not reservation.reservation_line_ids.filtered(
            lambda x: x.date == date
        ):
            raise UserError(_("Invalid date for reservation line "))

        if not self.browse(room.id):
            raise UserError(_("The room does not exist"))
        pms_property = reservation.pms_property_id.with_context(
            checkin=date,
            checkout=(
                datetime.datetime(year=date.year, month=date.month, day=date.day)
                + datetime.timedelta(days=1)
            ).date(),
            current_lines=reservation.reservation_line_ids.ids,
            pricelist_id=reservation.pricelist_id.id,
            real_avail=True,
            class_id=reservation.room_type_id.class_id.id,
        )
        rooms_available = pms_property.free_room_ids

        if room not in rooms_available:
            raise UserError(_("The room is not available"))

        reservation.reservation_line_ids.filtered(
            lambda x: x.date == date
        ).room_id = room.id

    @api.model
    def reservation_join(self, reservation, room):
        pms_property = reservation.pms_property_id
        pms_property = pms_property.with_context(
            checkin=reservation.checkin,
            checkout=reservation.checkout,
            current_lines=reservation.reservation_line_ids.ids,
            pricelist_id=reservation.pricelist_id.id,
            real_avail=True,
        )
        rooms_available = pms_property.free_room_ids

        if room in rooms_available:
            for line in (
                self.env["pms.reservation"]
                .search([("id", "=", reservation.id)])
                .reservation_line_ids
            ):
                line.room_id = room.id
        else:
            raise UserError(_("Room {} not available.".format(room.name)))

    @api.model
    def reservations_swap(self, checkin, checkout, source, target):
        dates = [
            checkin + datetime.timedelta(days=x)
            for x in range(0, (checkout - checkin).days + 1)
        ]
        lines = self.env["pms.reservation.line"].search_count(
            [("room_id", "=", source), ("date", "in", dates)]
        )
        if not lines:
            raise UserError(_("There's no reservations lines with provided room"))

        for date_iterator in dates:
            line_room_source = self.env["pms.reservation.line"].search(
                [("date", "=", date_iterator), ("room_id", "=", source)]
            )
            line_room_target = self.env["pms.reservation.line"].search(
                [("date", "=", date_iterator), ("room_id", "=", target)]
            )
            if line_room_source and line_room_target:
                line_room_target.with_context(
                    avoid_availability_check=True
                ).room_id = source
                line_room_source.with_context(
                    avoid_availability_check=True
                ).room_id = target
            elif line_room_source:
                line_room_source.room_id = target
            elif line_room_target:
                line_room_target.room_id = source

    def action_split(self):
        for record in self:
            for line in record.reservation_lines_to_change:
                self.reservation_split(
                    record.reservation_id,
                    line.date,
                    line.room_id,
                )

    def action_join(self):
        for record in self:
            self.reservation_join(record.reservation_id, record.room_target)

    def action_swap(self):
        self.reservations_swap(
            self.checkin, self.checkout, self.room_source.id, self.room_target.id
        )


class ReservationLinesToSplit(models.TransientModel):
    _name = "pms.wizard.reservation.lines.split"
    _description = "Lines available to split"

    reservation_wizard_id = fields.Many2one(
        string="Reservation Wizard",
        comodel_name="pms.reservation.split.join.swap.wizard",
    )
    date = fields.Date(
        string="Date",
    )
    room_id = fields.Many2one(
        string="Room",
        comodel_name="pms.room",
        domain="[('id', 'in', allowed_room_ids)]",
    )
    allowed_room_ids = fields.Many2many(
        string="Allowed Rooms",
        help="It contains all available rooms for this line",
        store=True,
        comodel_name="pms.room",
        compute="_compute_allowed_room_ids",
        # readonly=False
    )

    @api.depends(
        "date",
        "room_id",
        "reservation_wizard_id.reservation_id.pricelist_id",
    )
    def _compute_allowed_room_ids(self):
        for line in self:
            reservation = line.reservation_wizard_id.reservation_id
            rooms_available = False
            if line.date and reservation:
                if reservation.overbooking or reservation.state in ("cancel"):
                    line.allowed_room_ids = self.env["pms.room"].search(
                        [("active", "=", True)]
                    )
                    return
                pms_property = reservation.pms_property_id
                pms_property = pms_property.with_context(
                    checkin=line.date,
                    checkout=line.date + datetime.timedelta(days=1),
                    room_type_id=False,  # Allows to choose any available room
                    pricelist_id=reservation.pricelist_id.id,
                    real_avail=True,
                    class_id=reservation.room_type_id.class_id.id,
                )
                rooms_available = pms_property.free_room_ids
                rooms_available += line.room_id
                line.allowed_room_ids = rooms_available
            else:
                line.allowed_room_ids = False
