# © 2022 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from psycopg2.extensions import AsIs

from odoo import api, fields, models, tools


class ScopContributionReport(models.Model):
    _name = "scop.contribution.report"
    _description = "Vue cotisations"
    _auto = False
    _order = "year desc, partner_id"

    name = fields.Char(compute="_compute_name")
    year = fields.Char("Année de cotisation")
    source = fields.Selection(
        string="Source",
        selection=[("odoo", "Odoo")],
        required=True,
    )

    type_contribution_id = fields.Many2one(
        comodel_name="scop.contribution.type",
        string="Type de cotisation",
        readonly=True,
    )
    partner_id = fields.Many2one("res.partner", string="Partner", readonly=True)
    amount_called = fields.Float("Montant Appelé")
    amount_paid = fields.Float("Montant Payé")
    amount_due = fields.Float("Montant Restant")
    is_loss = fields.Boolean("Exonération/Perte", compute="_compute_is_loss")
    payments = fields.Html("Paiements", compute="_compute_payments")

    _depends = {
        "account.move": [
            "year",
            "type_contribution_id",
            "partner_id",
            "amount_total_signed",
            "amount_residual_signed",
            "state",
            "move_type",
            "is_contribution",
            "payment_state",
        ],
    }

    # ------------------------------------------------------
    # Sub Query
    # ------------------------------------------------------
    def _select_invoice(self):
        select_str = """
            SELECT
                'odoo' as source,
                CAST(i.year AS VARCHAR),
                i.type_contribution_id,
                i.partner_id,
                SUM(i.amount_total_signed) AS amount_called,
                SUM(i.amount_total_signed - i.amount_residual_signed) AS amount_paid,
                SUM(i.amount_residual_signed) AS amount_due
        """
        return select_str

    def _from_invoice(self):
        from_str = """
                    FROM
                        account_move i
                """
        return from_str

    def _where_invoice(self):
        where_str = """
            WHERE
                i.state = 'posted'
                AND i.move_type in ('out_invoice', 'out_refund')
                AND i.is_contribution = true
        """
        return where_str

    def _groupby_invoice(self):
        groupby_str = """
            GROUP BY
                i.year,
                i.type_contribution_id,
                i.partner_id
        """
        return groupby_str

    def _query_invoice(self):
        query = "(%s %s %s %s)" % (
            self._select_invoice(),
            self._from_invoice(),
            self._where_invoice(),
            self._groupby_invoice(),
        )
        return query

    def _subquery(self):
        return self._query_invoice()

    # ------------------------------------------------------
    # Main Query
    # ------------------------------------------------------
    def _select(self):
        select_str = """
            SELECT
                ROW_NUMBER() OVER(ORDER BY c.year, c.partner_id) AS id,
                c.source,
                c.year,
                c.type_contribution_id,
                c.partner_id,
                SUM(c.amount_called) AS amount_called,
                SUM(c.amount_paid) AS amount_paid,
                SUM(c.amount_due) AS amount_due
            FROM (
        """
        return select_str

    def _query_groupby(self):
        return """
        GROUP BY
            c.year,
            c.type_contribution_id,
            c.partner_id,
            c.source
        """

    def _query_order(self):
        return "ORDER BY c.year DESC"

    def _query(self):
        query = (
            self._select()
            + self._subquery()
            + ") c "
            + self._query_groupby()
            + self._query_order()
        )
        return query

    @api.model
    def init(self):
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            "CREATE or REPLACE VIEW %s as (%s)",
            (AsIs(self._table), AsIs(self._query())),
        )

    # ------------------------------------------------------
    # Computed fields
    # ------------------------------------------------------
    def _compute_is_loss(self):
        for contribution in self:
            contribution.is_loss = contribution._get_is_loss()

    def _compute_name(self):
        for contribution in self:
            contribution.name = (
                contribution.year
                + " - "
                + contribution.type_contribution_id.name
                + " - "
                + contribution.partner_id.name
            )

    def _compute_payments(self):
        for contribution in self:
            contribution.payments = contribution._get_payment()

    # ------------------------------------------------------
    # Business functions
    # ------------------------------------------------------
    def _get_is_loss(self):
        invoice_ids = (
            self.env["account.move"]
            .sudo()
            .search(
                [
                    ("year", "=", int(self.year)),
                    ("partner_id", "=", self.partner_id.id),
                    (
                        "type_contribution_id",
                        "=",
                        self.type_contribution_id.id,
                    ),
                ]
            )
        )
        refund_ids = invoice_ids.filtered(lambda i: i.move_type == "out_refund")
        if refund_ids:
            return True
        else:
            return False

    def _get_payment(self):
        self.ensure_one()
        payments_html = False
        if self.source == "odoo":
            invoice_ids = self.get_invoice_contribution()
            payment_ids = invoice_ids.mapped("move_line_payment_ids")
            if payment_ids:
                payments = payment_ids.mapped(
                    lambda p: {
                        "date": p.date,
                        "name": p.name,
                        "ref": p.ref,
                        "credit": p.credit,
                        "class": "",
                    }
                    if not p.move_id.move_type in ("out_invoice", "out_refund")
                    else {
                        "date": p.date,
                        "name": p.name,
                        "ref": "",
                        "credit": p.credit,
                        "class": "text-danger",
                    }
                )
                payments_html = self._get_html_table(payments)
        if not payments_html:
            payments_html = (
                "<p>Il n'y a pas de paiements associés à cette cotisation</p>"
            )
        return payments_html

    def get_invoice_contribution(self):
        invoice_ids = (
            self.env["account.move"]
            .sudo()
            .search(
                self._get_contribution_domain()
            )
        )
        return invoice_ids

    def _get_html_table(self, payments):
        """
        :param payments: list of dict {date, name, ref, amount}
        @return: HTML table with payments
        """
        start_html = """
            <table class='table table-sm table-striped table-hover table-bordered'>
                <thead><tr>
                    <th>Date</th>
                    <th>Libellé</th>
                    <th>Référence</th>
                    <th>Montant</th>
                </tr></thead>
                <tbody>
        """
        content_html = ""
        for payment in payments:
            content_html += """
                <tr class='%s'>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td class='text-right'>%.2f €</td>
                </tr>
            """ % (
                payment.get("class", ""),
                payment.get("date", "").strftime("%d/%m/%Y"),
                payment.get("name", ""),
                payment.get("ref", ""),
                payment.get("credit", 0.0),
            )

        end_html = "</tbody></table>"
        return start_html + content_html + end_html

    def _get_contribution_domain(self):
        return [
            ("year", "=", int(self.year)),
            ("partner_id", "=", self.partner_id.id),
            (
                "type_contribution_id",
                "=",
                self.type_contribution_id.id,
            ),
            ("move_type", "=", "out_invoice"),
            ("state", "=", "posted"),
        ]