# Part of Odoo. See LICENSE file for full copyright and licensing details.

import logging

from odoo import fields

_logger = logging.getLogger(__name__)


class BaseBrowsableObject:
    def __init__(self, vals_dict):
        self.__dict__["base_fields"] = ["base_fields", "dict"]
        self.dict = vals_dict

    def __getattr__(self, attr):
        return attr in self.dict and self.dict.__getitem__(attr) or 0.0

    def __setattr__(self, attr, value):
        _fields = self.__dict__["base_fields"]
        if attr in _fields:
            return super().__setattr__(attr, value)
        self.__dict__["dict"][attr] = value

    def __str__(self):
        return str(self.__dict__)


# These classes are used in the _get_payslip_lines() method
class BrowsableObject(BaseBrowsableObject):
    def __init__(self, employee_id, vals_dict, env):
        super().__init__(vals_dict)
        self.base_fields += ["employee_id", "env"]
        self.employee_id = employee_id
        self.env = env


class InputLine(BrowsableObject):
    """a class that will be used into the python code, mainly for
    usability purposes"""

    def sum(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """
            SELECT sum(amount) as sum
            FROM hr_payslip as hp, hr_payslip_input as pi
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s
            AND hp.id = pi.payslip_id AND pi.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        return self.env.cr.fetchone()[0] or 0.0


class WorkedDays(BrowsableObject):
    """a class that will be used into the python code, mainly for
    usability purposes"""

    def _sum(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """
            SELECT sum(number_of_days) as number_of_days,
             sum(number_of_hours) as number_of_hours
            FROM hr_payslip as hp, hr_payslip_worked_days as pi
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s
            AND hp.id = pi.payslip_id AND pi.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        return self.env.cr.fetchone()

    def sum(self, code, from_date, to_date=None):
        res = self._sum(code, from_date, to_date)
        return res and res[0] or 0.0

    def sum_hours(self, code, from_date, to_date=None):
        res = self._sum(code, from_date, to_date)
        return res and res[1] or 0.0


class Payslips(BrowsableObject):
    """a class that will be used into the python code, mainly for
    usability purposes"""

    def sum_rule(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT sum(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def sum(self, code, from_date, to_date=None):
        _logger.warning(
            "Payslips Object: sum() method is DEPRECATED. Use sum_rule() instead."
        )
        return self.sum_rule(code, from_date, to_date)

    def average_rule(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT avg(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def average_rule_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT avg(total) FROM (SELECT max(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def max_rule(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT max(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def max_rule_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT max(total) FROM (SELECT max(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def min_rule(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT min(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def min_rule_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()
        self.env.cr.execute(
            """SELECT min(total) FROM (SELECT max(case when hp.credit_note = False then
            (pl.total) else (-pl.total) end)
                    FROM hr_payslip as hp, hr_payslip_line as pl
                    WHERE hp.employee_id = %s AND hp.state = 'done'
                    AND hp.date_from >= %s AND hp.date_to <= %s AND
                     hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""",
            (self.employee_id, from_date, to_date, code),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def sum_category(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT sum(case when hp.credit_note is not True then
            (pl.total) else (-pl.total) end)
            FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
            AND rc.id = pl.category_id AND rc.code in %s""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def average_category(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT avg(case when hp.credit_note is not True then
            (pl.total) else (-pl.total) end)
            FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
            AND rc.id = pl.category_id AND rc.code in %s""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def average_category_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT avg(total) FROM (
                SELECT DATE_TRUNC('month',hp.date_from) AS date_month,
                    sum(case when hp.credit_note is not True then
                        (pl.total) else (-pl.total) end) AS total
                FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
                WHERE hp.employee_id = %s AND hp.state = 'done'
                AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
                AND rc.id = pl.category_id AND rc.code in %s
                GROUP BY date_month) AS monthly_sum""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def max_category(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT max(case when hp.credit_note is not True then
            (pl.total) else (-pl.total) end)
            FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
            AND rc.id = pl.category_id AND rc.code in %s""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def max_category_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT max(total) FROM (
                SELECT DATE_TRUNC('month',hp.date_from) AS date_month,
                    sum(case when hp.credit_note is not True then
                        (pl.total) else (-pl.total) end) AS total
                FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
                WHERE hp.employee_id = %s AND hp.state = 'done'
                AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
                AND rc.id = pl.category_id AND rc.code in %s
                GROUP BY date_month) AS monthly_sum""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def min_category(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT min(case when hp.credit_note is not True then
            (pl.total) else (-pl.total) end)
            FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
            WHERE hp.employee_id = %s AND hp.state = 'done'
            AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
            AND rc.id = pl.category_id AND rc.code in %s""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0

    def min_category_monthly(self, code, from_date, to_date=None):
        if to_date is None:
            to_date = fields.Date.today()

        hierarchy_codes = (
            self.env["hr.salary.rule.category"]
            .search([("code", "=", code)])
            .children_ids.mapped("code")
        )
        hierarchy_codes.append(code)

        self.env.cr.execute(
            """SELECT min(total) FROM (
                SELECT DATE_TRUNC('month',hp.date_from) AS date_month,
                    sum(case when hp.credit_note is not True then
                        (pl.total) else (-pl.total) end) AS total
                FROM hr_payslip hp, hr_payslip_line pl, hr_salary_rule_category rc
                WHERE hp.employee_id = %s AND hp.state = 'done'
                AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id
                AND rc.id = pl.category_id AND rc.code in %s
                GROUP BY date_month) AS monthly_sum""",
            (self.employee_id, from_date, to_date, tuple(hierarchy_codes)),
        )
        res = self.env.cr.fetchone()
        return res and res[0] or 0.0
