from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.model import Workflow
from trytond.report import Report
from datetime import date, timedelta

__all__ = ['Party', 'CreditRiskRule', 'CreditRiskReport']
__metaclass__ = PoolMeta

class Party(metaclass=PoolMeta):
    "Extend party with credit risk fields"
    __name__ = 'party.party'

    credit_limit = fields.Numeric('Credit Limit', digits=(16, 2))
    credit_currency = fields.Many2One('currency.currency', 'Credit Currency')
    credit_exposure = fields.Function(
        fields.Numeric('Credit Exposure', digits=(16, 2)),
        'get_credit_exposure'
    )
    aging_0_30 = fields.Function(
        fields.Numeric('Aging 0-30', digits=(16, 2)),
        'get_aging_buckets'
    )
    aging_31_60 = fields.Function(
        fields.Numeric('Aging 31-60', digits=(16, 2)),
        'get_aging_buckets'
    )
    aging_61_90 = fields.Function(
        fields.Numeric('Aging 61-90', digits=(16, 2)),
        'get_aging_buckets'
    )
    aging_90p = fields.Function(
        fields.Numeric('Aging >90', digits=(16, 2)),
        'get_aging_buckets'
    )
    utilization = fields.Function(
        fields.Numeric('Utilization %', digits=(5, 2)),
        'get_utilization'
    )
    risk_score = fields.Function(
        fields.Integer('Risk Score'),
        'get_risk_score'
    )
    risk_level = fields.Selection([
        (None,''),
        ('low', 'Low'),
        ('medium', 'Medium'),
        ('high', 'High'),
    ], 'Risk Level')

    acceptable_currencies = fields.One2Many(
        'party.acceptable.currency', 'party',
        'Acceptable Currencies'
    )

    payment_conditions = fields.One2Many(
        'party.payment.condition', 'party',
        'Payment Conditions'
    )

    internal_limit = fields.One2Many(
        'party.internal.limit', 'party',
        'Internal limits'
    )

    insurance_limit = fields.One2Many(
        'party.insurance.limit', 'party',
        'Insurance limits'
    )

    # -------------------------
    # Calculations
    # -------------------------
    def get_credit_exposure(self, name):
        """
        Sum of open receivable move lines amounts for this party.
        We take account.move.line where account.kind = 'receivable' and
        reconciliation is None (i.e. open).
        """
        MoveLine = Pool().get('account.move.line')
        # Search open receivable lines for this party
        lines = MoveLine.search([
            ('party', '=', self.id),
            #('account.kind', '=', 'receivable'),
            ('reconciliation', '=', None),
        ])
        amount = sum([l.amount or 0 for l in lines])
        return amount

    def get_aging_buckets(self, name):
        """
        Returns a tuple (a0, a1, a2, a3) corresponding to the bucket requested.
        We'll compute all buckets then return the requested field.
        """
        MoveLine = Pool().get('account.move.line')
        today = date.today()
        lines = MoveLine.search([
            ('party', '=', self.id),
            #('account.kind', '=', 'receivable'),
            ('reconciliation', '=', None),
        ])
        a0 = a1 = a2 = a3 = 0
        for l in lines:
            # Try to use 'due_date' if present, else 'date'
            line_date = getattr(l, 'due_date', None) or getattr(l, 'date', None) or today
            if not line_date:
                line_date = today
            days = (today - line_date).days
            amt = l.amount or 0
            if days <= 30:
                a0 += amt
            elif days <= 60:
                a1 += amt
            elif days <= 90:
                a2 += amt
            else:
                a3 += amt

        if name == 'aging_0_30':
            return a0
        if name == 'aging_31_60':
            return a1
        if name == 'aging_61_90':
            return a2
        if name == 'aging_90p':
            return a3
        return 0

    def get_utilization(self, name):
        limit_ = self.credit_limit or 0
        exposure = self.get_credit_exposure('credit_exposure')
        if not limit_ or limit_ == 0:
            return 0
        util = (exposure / limit_) * 100
        return float(round(util, 2))

    def get_risk_score(self, name):
        """
        Compute a simple risk score:
        - base on utilization and >90 days exposure
        - more sophisticated logic can be plugged via CreditRiskRule.
        """
        score = 0
        util = self.get_utilization('utilization')  # percentage
        if util > 100:
            score += 50
        elif util > 80:
            score += 30
        elif util > 50:
            score += 10

        overdue = self.get_aging_buckets('aging_90p')
        if overdue > 0:
            # scale by overdue relative to limit
            limit = self.credit_limit or 1
            score += int(min(40, (float(overdue) / float(limit)) * 100))

        # cap
        if score > 100:
            score = 100
        # derive level
        if score >= 70:
            level = 'high'
        elif score >= 30:
            level = 'medium'
        else:
            level = 'low'

        # ensure we persist risk_level locally (not stored field; update if present)
        try:
            # If we want to store it persistently, we would need a stored field
            self.risk_level = level
        except Exception:
            pass

        return int(score)

class CreditRiskRule(ModelSQL, ModelView):
    """
    Simple rules table to tweak scoring thresholds.
    Example usage: company-specific thresholds.
    """
    __name__ = 'credit.risk.rule'

    name = fields.Char('Name', required=True)
    company = fields.Many2One('company.company', 'Company')
    max_utilization_ok = fields.Float('Max Utilization OK', digits=(5, 2))
    warn_utilization = fields.Float('Warn Utilization', digits=(5, 2))
    block_on_over_limit = fields.Boolean('Block on Over Limit')

    def __str__(self):
        return self.name

class PartyInternalLimit(ModelSQL, ModelView):
    "Party Internal Limit"
    __name__ = 'party.internal.limit'

    party = fields.Many2One('party.party', 'Party', required=True)
    amount = fields.Numeric('Amount', digits=(16, 2), required=True)
    currency = fields.Many2One('currency.currency', 'Currency', required=True)
    date_from = fields.Date('From', required=True)
    date_to = fields.Date('To', required=True)
    remarks = fields.Char('Remarks')

class PartyInsuranceLimit(ModelSQL, ModelView):
    "Party Insurance Limit"
    __name__ = 'party.insurance.limit'

    party = fields.Many2One('party.party', 'Party', required=True)
    amount = fields.Numeric('Amount', digits=(16, 2))
    currency = fields.Many2One('currency.currency', 'Currency')
    date_from = fields.Date('From')
    date_to = fields.Date('To')
    insurer = fields.Char('Insurance')
    policy = fields.Char('Policy')

class PartyPaymentCondition(ModelSQL, ModelView):
    "Party Payment Condition"
    __name__ = 'party.payment.condition'

    party = fields.Many2One('party.party', 'Party', required=True)
    payment_term = fields.Many2One('account.invoice.payment_term','Payment Term', required=True)
    remaining_risk = fields.Numeric(
        'Remaining Risk %', digits=(5, 2)
    )
    remarks = fields.Text('Remarks')

class PartyAcceptableCurrency(ModelSQL, ModelView):
    "Party Acceptable Currency"
    __name__ = 'party.acceptable.currency'

    party = fields.Many2One(
        'party.party', 'Party', required=True, ondelete='CASCADE'
    )
    currency = fields.Many2One(
        'currency.currency', 'Currency', required=True
    )
    haircut = fields.Numeric('Haircut %', digits=(5, 2))

# class CreditRiskReport(ModelView):
#     """
#     A report model (non stored) to expose a table-query-like result via context.
#     The get_context method returns 'rows' which is a list of dicts ready for templates.
#     """
#     __name__ = 'party.credit_risk_report'

#     start_date = fields.Date('Start Date')
#     end_date = fields.Date('End Date')
#     company = fields.Many2One('company.company', 'Company')

#     @classmethod
#     def get_report_data(cls, start=None, end=None, company=None):
#         """
#         Return aggregated rows:
#         - party_id, name, credit_limit, exposure, utilization, aging buckets, risk_score, risk_level, status
#         We'll fetch parties with at least some receivable or credit limit set.
#         """
#         Party = Pool().get('party.party')
#         MoveLine = Pool().get('account.move.line')

#         # build parties list: those with credit limit or receivable lines
#         parties = Party.search([
#             ('credit_limit', '!=', None)
#         ])
#         # also include parties with receivable move lines
#         ml_parties = MoveLine.search([('account.kind', '=', 'receivable')])
#         ml_party_ids = set([l.party.id for l in ml_parties if l.party])
#         for pid in ml_party_ids:
#             try:
#                 p = Party(pid)
#                 if p not in parties:
#                     parties.append(p)
#             except Exception:
#                 pass

#         rows = []
#         for p in parties:
#             exposure = p.get_credit_exposure('credit_exposure')
#             a0 = p.get_aging_buckets('aging_0_30')
#             a1 = p.get_aging_buckets('aging_31_60')
#             a2 = p.get_aging_buckets('aging_61_90')
#             a3 = p.get_aging_buckets('aging_90p')
#             utilization = p.get_utilization('utilization')
#             score = p.get_risk_score('risk_score')
#             level = getattr(p, 'risk_level', None) or (
#                 'high' if score >= 70 else ('medium' if score >= 30 else 'low')
#             )
#             status = 'OK'
#             if p.credit_limit and exposure > p.credit_limit:
#                 status = 'OVER_LIMIT'

#             rows.append({
#                 'party_id': p.id,
#                 'party_name': p.name,
#                 'credit_limit': p.credit_limit or 0,
#                 'exposure': exposure,
#                 'aging_0_30': a0,
#                 'aging_31_60': a1,
#                 'aging_61_90': a2,
#                 'aging_90p': a3,
#                 'utilization_pct': utilization,
#                 'risk_score': score,
#                 'risk_level': level,
#                 'status': status,
#             })
#         # Optionally sort by risk_score desc
#         rows = sorted(rows, key=lambda r: r['risk_score'], reverse=True)
#         return rows

#     @classmethod
#     def get_context(cls, records, data):
#         """
#         Called by Report action to supply context.
#         We will return dict with 'rows' and some summary aggregates.
#         """
#         start = data.get('start_date') if data else None
#         end = data.get('end_date') if data else None
#         company = data.get('company') if data else None

#         rows = cls.get_report_data(start=start, end=end, company=company)
#         total_limit = sum([r['credit_limit'] for r in rows])
#         total_exposure = sum([r['exposure'] for r in rows])
#         over_limit = [r for r in rows if r['status'] == 'OVER_LIMIT']
#         ctx = {
#             'rows': rows,
#             'total_limit': total_limit,
#             'total_exposure': total_exposure,
#             'count_over_limit': len(over_limit),
#             'image_path': '/mnt/data/f9976760-c385-48ed-b51c-fa5868a0f0ab.png', # optional
#         }
#        return ctx
