# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from odoo.tools import date_utils
from dateutil.relativedelta import relativedelta


class ConfirmBenefitExpense(models.Model):
    _name = 'confirm.benefit.expense'
    _description = 'Confirm Benefit Expense'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = "family_expense_seq desc"

    # region [Default Methods]

    def _default_start_date(self):
        today = fields.Date.today()
        start_date = date_utils.start_of(today, 'month')
        return start_date

    def _default_end_date(self):
        today = fields.Date.today()
        end_date = date_utils.end_of(today, 'month')
        return end_date

    # endregion [Default Methods]

    family_expense_seq = fields.Char(string="Number", copy=False, readonly=True,
                                     default=lambda x: _('New'))
    state = fields.Selection(selection=[
        ('draft', 'Draft'),
        ('calculated', 'Calculated'),
        ('assistant_general_manager', 'Waiting For The Assistant General Manager'),
        ('accounting_approve', 'Accounting Approve'),
        ('cancel', 'Cancelled'),
        ('confirm', 'Confirmed'),
    ], string='Status', default='draft', required=True, copy=False, tracking=True)
    name = fields.Char(string="Name", states={'confirm': [('readonly', True)]}, copy=False)
    date = fields.Date(string="Date", default=fields.Date.context_today, required=False,
                       states={'confirm': [('readonly', True)]})
    start_date = fields.Date(string="Start Date", default=_default_start_date, required=True)
    end_date = fields.Date(string="End Date", required=True, default=_default_end_date)

    family_ids = fields.Many2many(comodel_name='grant.benefit', relation='benefit_expense_grant_rel',
                                  column1='expense_id',
                                  column2='family_id', string='Families', states={'confirm': [('readonly', True)]},
                                  copy=False)
    benefit_expense_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='confirm_expense_id',
                                               string='Benefit Expense Lines')
    othaim_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='confirm_expense_id',
                                      string='Othaim Lines', domain=[('meal_card', '=', True)])
    cash_expense = fields.Boolean(string='Include Cash Expense', default=True, states={'confirm': [('readonly', True)]})
    meal_expense = fields.Boolean(string='Include Meal Expense', default=True, states={'confirm': [('readonly', True)]})
    cloth_expense = fields.Boolean(string='Include Clothing Expense', default=True,
                                   states={'confirm': [('readonly', True)]})
    payment_order_id = fields.Many2one('payment.orders', string='Payment Order', ondelete="set null", copy=False)
    move_id = fields.Many2one('account.move')
    available_payment_method_line_ids = fields.Many2many(comodel_name='account.payment.method.line')
    family_monthly_income = fields.Float(string="Total Monthly Income", compute='_get_family_monthly_values',
                                         store=True)
    family_monthly_meals = fields.Float(string="Total Monthly Meals", compute='_get_family_monthly_values', store=True)
    family_monthly_clotting = fields.Float(string="Total Monthly Clotting", compute='_get_family_monthly_values',
                                           store=True)
    family_monthly_othaime = fields.Float(string="Total Othaim", compute='_get_family_monthly_values', store=True)
    family_monthly_total = fields.Float(string="Total", compute='_get_family_monthly_values', store=True)
    branch_custom_ids = fields.Many2many(comodel_name='branch.settings', relation='confirm_benefit_expense_branch_rel',
                                         column1='expense_id', column2='branch_id', string="Branches",
                                         domain="[('has_employees', '=', True)]")
    family_domain_ids = fields.Many2many(comodel_name='grant.benefit', compute='_compute_domain_ids')
    company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
    currency_id = fields.Many2one(comodel_name='res.currency', string="Company Currency",
                                  related='company_id.currency_id')
    payment_state = fields.Selection(string='Payment State', selection=[
        ('none', 'None'),
        ('waiting', 'Waiting Payment'),
        ('done', 'Done Payment'), ], copy=False, compute="_compute_payment_move_state", store=True)
    move_state = fields.Selection(string='Move State', selection=[
        ('none', 'None'),
        ('waiting', 'Waiting Payment'),
        ('done', 'Done Payment'), ], copy=False, compute="_compute_payment_move_state", store=True)
    family_count_expense = fields.Integer(string="Family Count (Monthly Expense)",
                                          compute='_get_family_monthly_values', store=True)

    family_count_othaim = fields.Integer(string="Family Count (Othaim)", compute='_get_family_monthly_values',
                                         store=True)
    member_count_expense = fields.Integer(string="Member Count (Monthly Expense)", compute='_get_family_monthly_values',
                                          store=True, )

    member_count_othaim = fields.Integer(string="Member Count (Othaim)", compute='_get_family_monthly_values',
                                         store=True, )
    is_return_calculation = fields.Boolean(string="Return Calculation Mode", default=False,
                                           help="Enable to calculate returned amounts instead of regular monthly expense."
                                           )
    return_expense_line_ids = fields.One2many(comodel_name='benefit.expense.line', inverse_name='return_confirm_id',
                                              string="Selected Return Lines",
                                              domain="[('is_return', '=', True)]", )
    line_domain_ids = fields.Many2many(comodel_name='benefit.expense.line', compute='_compute_domain_ids',
                                       string="Return Line Domain",
                                       )
    has_draft_return_lines = fields.Boolean(string="Has Draft Return Lines",
                                              compute='_compute_has_draft_return_lines', )

    @api.depends('return_expense_line_ids')
    def _compute_has_draft_return_lines(self):
        for rec in self:
            if rec.return_expense_line_ids:
                draft_lines = rec.return_expense_line_ids.filtered(lambda l: l.state == 'draft')
                rec.has_draft_return_lines = bool(draft_lines)
            else:
                rec.has_draft_return_lines = False

    @api.depends('payment_order_id', 'payment_order_id.state', 'move_id', 'move_id.state')
    def _compute_payment_move_state(self):
        for rec in self:
            payment_state = 'none'
            move_state = 'none'
            if rec.payment_order_id:
                if rec.payment_order_id.state == "done":
                    payment_state = "done"
                else:
                    payment_state = "waiting"
            if rec.move_id:
                if rec.move_id.state == "posted":
                    move_state = "done"
                else:
                    move_state = "waiting"
            rec.move_state = move_state
            rec.payment_state = payment_state
            if rec.move_state == 'done' and rec.payment_state == 'done':
                rec.state = 'confirm'

    def _get_month_count(self):
        self.ensure_one()
        if not self.start_date or not self.end_date:
            return 1

        start = fields.Date.from_string(self.start_date)
        end = fields.Date.from_string(self.end_date)

        diff = relativedelta(end, start)
        months = diff.years * 12 + diff.months + 1

        return max(1, months)

    @api.model
    def create(self, vals):
        res = super(ConfirmBenefitExpense, self).create(vals)
        if not res.family_ids:
            raise UserError(_('Select Family'))
        if not res.family_expense_seq or res.family_expense_seq == _('New'):
            res.family_expense_seq = self.env['ir.sequence'].sudo().next_by_code('family.expense.sequence') or _('New')
        return res

    def _update_benefit_expense_lines(self):
        self.ensure_one()
        month_count = self._get_month_count()
        for line in self.benefit_expense_line_ids:
            family = line.family_id
            income, meals, clotting, othaim = 0, 0, 0, 0
            if not family:
                continue
            monthly_meals = 0.0 if family.meal_card else family.family_monthly_meals
            othaime = family.family_monthly_meals if family.meal_card else 0.0
            if self.cash_expense:
                income = family.family_monthly_income * month_count
            if self.meal_expense:
                meals = monthly_meals * month_count
                othaim = othaime * month_count
            if self.cloth_expense:
                clotting = family.family_monthly_clotting * month_count
            vals = {
                'branch_id': family.branch_custom_id.id,
                'family_category_id': family.benefit_category_id.id,
                'meal_card': family.meal_card,
                'benefit_member_count': family.benefit_member_count,
                'start_date': self.start_date,
                'end_date': self.end_date,
                'family_monthly_income': income,
                'family_monthly_meals': meals,
                'family_monthly_clotting': clotting,
                'family_monthly_othaime': othaim,
            }
            line.write(vals)

    def _calculate_return_lines(self):
        self.ensure_one()
        return_lines = self.return_expense_line_ids
        if not return_lines:
            raise UserError(_("Please select at least one return line to calculate."))

        if return_lines.filtered(lambda l: l.state == 'waiting_processing'):
            raise UserError(
                _("You cannot calculate because some return lines are still in 'Waiting Processing' state."))

        self.benefit_expense_line_ids.unlink()
        lines = []
        for line in return_lines:
            lines.append((0, 0, {
                'family_id': line.family_id.id,
                'branch_id': line.branch_id.id,
                'family_category_id': line.family_category_id.id,
                'meal_card': line.meal_card,
                'benefit_member_count': line.benefit_member_count,
                'start_date': self.start_date,
                'end_date': self.end_date,
                'family_monthly_income': line.family_monthly_income,
                'family_monthly_meals': line.family_monthly_meals,
                'family_monthly_clotting': line.family_monthly_clotting,
                'family_monthly_othaime': 0,
            }))
        self.benefit_expense_line_ids = lines

    def action_calculate(self):
        for rec in self:
            if rec.state != 'draft':
                raise UserError(_("You can only calculate in draft state."))

            families = rec.family_ids
            if not families:
                raise UserError(_("Please select at least one family to calculate."))

            if not rec.cash_expense and not rec.meal_expense and not rec.cloth_expense:
                raise UserError(_("At least one expense type should be selected."))

            if rec.is_return_calculation:
                rec._calculate_return_lines()
            else:
                rec.benefit_expense_line_ids.unlink()
                lines = []
                for fam in families:
                    vals = {
                        'confirm_expense_id': rec.id,
                        'family_id': fam.id,
                        'start_date': rec.start_date,
                        'end_date': rec.end_date,
                    }
                    lines.append((0, 0, vals))

                rec.write({'benefit_expense_line_ids': lines})
                rec._update_benefit_expense_lines()
            rec.state = 'calculated'

    def action_recalculate(self):
        for rec in self:
            if rec.state != 'calculated':
                raise UserError(_("You can only recalculate when status is 'Calculated'."))
            if rec.is_return_calculation:
                rec._calculate_return_lines()
            else:
                rec._update_benefit_expense_lines()

    @api.depends('is_return_calculation', 'branch_custom_ids', 'start_date', 'end_date')
    def _compute_domain_ids(self):
        for rec in self:
            Line = self.env['benefit.expense.line']
            validation_setting = self.env["family.validation.setting"].search([], limit=1)
            if rec.is_return_calculation:
                domain = [
                    ('start_date', '<=', rec.end_date),
                    ('end_date', '>=', rec.start_date),
                    ('is_return', '=', True),
                    ('return_reason_id', '!=', False),
                    ('return_confirm_id', '=', False),
                ]
                if rec.branch_custom_ids:
                    domain.append(('branch_id', 'in', rec.branch_custom_ids.ids))

                return_lines = Line.search(domain)
                rec.line_domain_ids = return_lines
                rec.family_domain_ids = return_lines.mapped('family_id')
            else:
                # Define base domain for family selection

                base_domain = ['|', ('state', '=', 'second_approve'), '&',
                               ('state', 'in', ('waiting_approve', 'first_approve')), ('action_type', '=', 'suspended')]
                if rec.branch_custom_ids:
                    base_domain.append(('branch_custom_id', 'in', rec.branch_custom_ids.ids))
                min_income = validation_setting.benefit_category_ids.mapped('mini_income_amount')
                max_income = validation_setting.benefit_category_ids.mapped('max_income_amount')
                benefit_category_ids = validation_setting.benefit_category_ids
                base_domain.extend([('member_income', '>=', min(min_income)), ('member_income', '<=', max(max_income))])
                if benefit_category_ids:
                    base_domain.extend([('benefit_category_id', 'in', benefit_category_ids.ids)])
                else:
                    base_domain.extend([('benefit_category_id', '!=', False)])
                if rec.start_date and rec.end_date:
                    conflicting_records = self.search([
                        ('id', '!=', rec._origin.id),
                        ('start_date', '<=', rec.end_date),
                        ('end_date', '>=', rec.start_date),
                    ])

                    if conflicting_records:
                        conflicting_family_ids = conflicting_records.mapped('family_ids').ids
                        base_domain.append(('id', 'not in', conflicting_family_ids))

                rec.family_domain_ids = self.env['grant.benefit'].search(base_domain)
                rec.line_domain_ids = self.env['benefit.expense.line'].browse([])

    @api.onchange('branch_custom_ids')
    def _onchange_branch_custom_ids(self):
        if self.branch_custom_ids:
            allowed_families = self.env['grant.benefit'].search([
                ('id', 'in', self.family_ids.ids),
                ('branch_custom_id', 'in', self.branch_custom_ids.ids),
            ])
            self.family_ids = [(6, 0, allowed_families.ids)]
        else:
            self.family_ids = [(5, 0, 0)]

    def unlink(self):
        for rec in self:
            if rec.state not in ['draft']:
                raise UserError(_('This record can only be deleted in draft state.'))
        return super(ConfirmBenefitExpense, self).unlink()

    @api.depends('benefit_expense_line_ids')
    def _get_family_monthly_values(self):
        for rec in self:
            lines = rec.benefit_expense_line_ids
            rec.family_monthly_income = sum(lines.mapped('family_monthly_income'))
            rec.family_monthly_meals = sum(lines.mapped('family_monthly_meals'))
            rec.family_monthly_clotting = sum(lines.mapped('family_monthly_clotting'))
            rec.family_monthly_othaime = sum(lines.mapped('family_monthly_othaime'))
            rec.family_monthly_total = rec.family_monthly_income + rec.family_monthly_meals + rec.family_monthly_clotting

            othaim_lines = lines.filtered('meal_card')
            rec.family_count_expense = len(lines.mapped('family_id'))
            rec.member_count_expense = sum(lines.mapped('benefit_member_count'))

            rec.family_count_othaim = len(othaim_lines.mapped('family_id'))
            rec.member_count_othaim = sum(othaim_lines.mapped('benefit_member_count'))

    def action_assistant_manager(self):
        for family in self.family_ids:
            if self.end_date and family.last_disbursement_date:
                if self.end_date > family.last_disbursement_date:
                    family.last_disbursement_date = self.end_date
            else:
                family.last_disbursement_date = self.end_date

        self.state = 'assistant_general_manager'

    def action_accounting_approve(self):
        self.sudo().action_accounting_transfer()
        self.sudo().state = 'accounting_approve'

    def action_send_to_researcher(self):
        for rec in self:
            if not rec.is_return_calculation:
                raise UserError(_("This action is only available for return calculations."))

            return_lines = rec.return_expense_line_ids.filtered(lambda l: l.state == 'draft')

            if not return_lines:
                raise UserError(_("No return lines in draft state to send to specialist."))
            lines_without_researcher = self.env['benefit.expense.line']

            for line in return_lines:
                family = line.family_id
                researcher = family.researcher_id if hasattr(family, 'researcher_id') else False

                if not researcher:
                    lines_without_researcher |= line
                    continue
                line.write({
                    'researcher_id': researcher.id,
                    'state': 'waiting_processing'
                })
            if lines_without_researcher:
                families_without_specialist = lines_without_researcher.mapped('family_id.name')
                raise UserError(_(
                    "The following families do not have an assigned researcher:\n%s\n\n"
                    "Please assign researchers to these families before sending."
                ) % ", ".join(families_without_specialist))

    def action_cancel(self):
        self.state = 'cancel'

    def action_reset_to_draft(self):
        self.payment_order_id.unlink()
        self.move_id.unlink()
        self.benefit_expense_line_ids.unlink()
        self.state = 'draft'

    def action_reset_to_calculated(self):
        self.ensure_one()
        return {
            'name': _('Reason for Return'),
            'type': 'ir.actions.act_window',
            'res_model': 'reason.for.return.wizard',
            'view_mode': 'form',
            'target': 'new',
        }

    def action_open_related_move_records(self):
        moves = self.move_id.ids
        return {
            'name': _('Vendor Bills'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move',
            'view_mode': 'tree,form',
            'domain': [('id', 'in', moves)],
        }

    def action_open_related_payment_orders(self):
        payment_orders = self.payment_order_id.ids
        return {
            'name': _('Payment Orders'),
            'type': 'ir.actions.act_window',
            'res_model': 'payment.orders',
            'view_mode': 'tree,form',
            'domain': [('id', 'in', payment_orders)],
        }

    def action_accounting_transfer(self):
        for rec in self:
            validation_setting = self.env["family.validation.setting"].search([], limit=1)
            lines = rec.benefit_expense_line_ids
            if not lines:
                raise UserError(_("Please make sure you have benefit expense lines."))

            families = lines.mapped('family_id')
            invalid_families = families.filtered(
                lambda f: f.state != 'second_approve'
                          or (f.state in ('waiting_approve', 'first_approve') and f.action_type == 'suspended')
            )
            if invalid_families:
                raise UserError(_(
                    "Some selected benefits are not in valid state or are suspended:\n%s"
                ) % ", ".join(invalid_families.mapped('name')))

            if not validation_setting.cash_expense_account_id or not validation_setting.meal_expense_account_id or not validation_setting.clothing_expense_account_id:
                raise UserError(_("Please configure the expense accounts in the validation settings."))

            credit_account_id = validation_setting.account_id.id

            if not credit_account_id:
                raise UserError(_("Please select credit account."))

            # todo if have paymnet or move dont create again

            # Create Payment Order for Benefit Expense
            payment_order = self.env['payment.orders'].create({
                'state': 'draft',
                'accountant_id': validation_setting.accountant_id.id,
                'benefit_expense_line_ids': [(6, 0, rec.benefit_expense_line_ids.ids)],
                'type': 'benefit_expense',
            })
            rec.payment_order_id = payment_order

            if not rec.is_return_calculation:
                # Create Vendor Bill for Meal Card Invoice(othaime)
                account_id = validation_setting.meal_expense_account_id
                invoice_lines = []
                if lines.filtered(lambda l: l.meal_card):
                    for line in lines.filtered(lambda l: l.meal_card):
                        family = line.family_id
                        invoice_lines.append((0, 0, {
                            'name': f'{family.name}/{family.code}',
                            'account_id': account_id.id,
                            'quantity': 1,
                            'benefit_family_id': family.id,
                            'price_unit': line.family_monthly_othaime,
                            'family_confirm_id': rec.id,
                            'analytic_account_id': family.branch_family_id.branch.analytic_account_id.id
                        }))
                    invoice_vals = {
                        'move_type': 'in_invoice',
                        'partner_id': validation_setting.meal_partner_id.id,
                        'invoice_date': rec.date,
                        'family_confirm_id': rec.id,
                        'benefit_family_ids': [(6, 0, rec.benefit_expense_line_ids.mapped('family_id').ids)],
                        'journal_id': validation_setting.journal_id.id,
                        'invoice_line_ids': invoice_lines,
                        'ref': rec.name,
                    }

                    invoice = self.env['account.move'].create(invoice_vals)
                    rec.move_id = invoice

        return True
