import logging
from odoo import http
from odoo.http import request
from odoo.addons.encoach_api.controllers.base import (
    jwt_required, _json_response, _error_response, _paginate, _get_json_body,
)

_get_json = _get_json_body

_logger = logging.getLogger(__name__)


def _serialize_plan(r):
    """Build a fees-plan record with REAL paid/remaining pulled from the
    linked account.move (invoice), not a hard-coded zero."""
    total = float(getattr(r, 'amount', 0) or 0)
    after_discount = float(getattr(r, 'after_discount_amount', 0) or total)
    invoice = r.invoice_id if hasattr(r, 'invoice_id') else False

    invoice_state = ''
    payment_state = 'not_paid'
    amount_residual = after_discount
    paid_amount = 0.0
    invoice_id = 0
    invoice_number = ''

    if invoice and invoice.exists():
        invoice_id = invoice.id
        invoice_state = invoice.state or ''
        payment_state = getattr(invoice, 'payment_state', '') or 'not_paid'
        amount_residual = float(getattr(invoice, 'amount_residual', after_discount) or 0)
        amount_total = float(getattr(invoice, 'amount_total', after_discount) or 0)
        paid_amount = max(0.0, amount_total - amount_residual)
        invoice_number = getattr(invoice, 'name', '') or ''

    return {
        'id': r.id,
        'student_id': r.student_id.id if getattr(r, 'student_id', False) else 0,
        'student_name': (
            r.student_id.partner_id.name
            if getattr(r, 'student_id', False) and r.student_id.partner_id else ''
        ),
        'course_id': r.course_id.id if getattr(r, 'course_id', False) else 0,
        'course_name': r.course_id.name if getattr(r, 'course_id', False) else '',
        'batch_id': r.batch_id.id if getattr(r, 'batch_id', False) else 0,
        'batch_name': r.batch_id.name if getattr(r, 'batch_id', False) else '',
        'product_id': r.product_id.id if getattr(r, 'product_id', False) else 0,
        'product_name': r.product_id.name if getattr(r, 'product_id', False) else '',
        'date': str(r.date) if getattr(r, 'date', False) else '',
        'discount': float(getattr(r, 'discount', 0) or 0),
        'total_amount': total,
        'after_discount_amount': after_discount,
        'paid_amount': round(paid_amount, 2),
        'remaining_amount': round(amount_residual, 2),
        'state': getattr(r, 'state', 'draft') or 'draft',
        'invoice_id': invoice_id,
        'invoice_number': invoice_number,
        'invoice_state': invoice_state,
        'payment_state': payment_state,
        'currency': r.currency_id.name if getattr(r, 'currency_id', False) else '',
    }


class FeesController(http.Controller):

    @http.route('/api/fees-plans', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_fees_plans(self, **kw):
        try:
            M = request.env['op.student.fees.details'].sudo()
            offset, limit, page = _paginate(kw)
            domain = []
            state = (kw.get('state') or '').strip()
            if state:
                domain.append(('state', '=', state))
            payment_state = (kw.get('payment_state') or '').strip()
            q = (kw.get('q') or '').strip()
            if q:
                domain += ['|',
                           ('student_id.partner_id.name', 'ilike', q),
                           ('product_id.name', 'ilike', q)]
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            items = [_serialize_plan(r) for r in recs]
            if payment_state:
                items = [i for i in items if i['payment_state'] == payment_state]
            return _json_response({
                'items': items, 'data': items,
                'total': total, 'page': page, 'size': limit,
            })
        except Exception as e:
            _logger.exception('list_fees_plans')
            return _error_response(str(e), 500)

    @http.route('/api/fees-plans/<int:fid>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_fees_plan(self, fid, **kw):
        try:
            rec = request.env['op.student.fees.details'].sudo().browse(fid)
            if not rec.exists():
                return _error_response('Not found', 404)
            return _json_response(_serialize_plan(rec))
        except Exception as e:
            _logger.exception('get_fees_plan')
            return _error_response(str(e), 500)

    @http.route('/api/fees-plans/<int:fid>/create-invoice', type='http',
                auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_invoice(self, fid, **kw):
        """Generate the accounting invoice for this fee line (uses the built-in
        OpenEduCat `get_invoice()` model method)."""
        try:
            rec = request.env['op.student.fees.details'].sudo().browse(fid)
            if not rec.exists():
                return _error_response('Fees plan not found', 404)
            if not rec.product_id:
                return _error_response('This fees plan has no product; cannot invoice.', 400)
            if float(rec.amount or 0) <= 0:
                return _error_response('Fees amount must be positive to invoice.', 400)

            # Auto-wire a default income account on the product/category if
            # the Odoo chart of accounts is set up but the product was seeded
            # without one. Otherwise OpenEduCat raises a UserError.
            prod_tmpl = rec.product_id.sudo().product_tmpl_id
            try:
                fp = prod_tmpl.get_product_accounts() if hasattr(
                    prod_tmpl, 'get_product_accounts') else {}
            except Exception:
                fp = {}
            if not (fp or {}).get('income'):
                AA = request.env['account.account'].sudo()
                # Odoo 19: account.account has a M2M `company_ids`.
                try:
                    income_acc = AA.search([
                        ('account_type', '=', 'income'),
                        ('company_ids', 'in', request.env.company.id),
                    ], limit=1)
                except Exception:
                    income_acc = AA.browse()
                if not income_acc:
                    income_acc = AA.search([('account_type', '=', 'income')], limit=1)
                if income_acc:
                    try:
                        prod_tmpl.with_company(request.env.company).write({
                            'property_account_income_id': income_acc.id,
                        })
                    except Exception:
                        _logger.exception('auto-wire income account')
            rec.get_invoice()
            rec.invalidate_recordset()
            return _json_response(_serialize_plan(rec))
        except Exception as e:
            _logger.exception('create_invoice')
            return _error_response(str(e), 500)

    @http.route('/api/fees-plans/<int:fid>/register-payment', type='http',
                auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def register_payment(self, fid, **kw):
        """Register a payment against the invoice linked to this fees plan.
        Body: { "amount": float, "journal_id": int (optional),
                 "payment_date": "YYYY-MM-DD" (optional), "memo": str (optional) }"""
        try:
            body = _get_json() or {}
            rec = request.env['op.student.fees.details'].sudo().browse(fid)
            if not rec.exists():
                return _error_response('Fees plan not found', 404)
            invoice = rec.invoice_id
            if not invoice or not invoice.exists():
                return _error_response('Create an invoice before registering a payment.', 400)
            if invoice.state == 'draft':
                invoice.sudo().action_post()

            amount = float(body.get('amount') or invoice.amount_residual or 0)
            if amount <= 0:
                return _error_response('Payment amount must be positive.', 400)

            payment_date = body.get('payment_date') or False
            memo = body.get('memo') or f"Payment for {rec.product_id.name or 'fees'}"

            journal_id = body.get('journal_id')
            if not journal_id:
                journal = request.env['account.journal'].sudo().search(
                    [('type', 'in', ('bank', 'cash'))], limit=1
                )
                if not journal:
                    return _error_response(
                        'No bank/cash journal configured. Open Accounting → Configuration → Journals.',
                        400)
                journal_id = journal.id

            wiz_vals = {
                'amount': amount,
                'journal_id': int(journal_id),
                'communication': memo,
            }
            if payment_date:
                wiz_vals['payment_date'] = payment_date
            wizard = request.env['account.payment.register'].sudo().with_context(
                active_model='account.move', active_ids=[invoice.id]
            ).create(wiz_vals)
            wizard.action_create_payments()
            invoice.invalidate_recordset()
            rec.invalidate_recordset()
            return _json_response(_serialize_plan(rec))
        except Exception as e:
            _logger.exception('register_payment')
            return _error_response(str(e), 500)

    @http.route('/api/student-fees', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_student_fees(self, **kw):
        try:
            M = request.env['op.student.fees.details'].sudo()
            offset, limit, page = _paginate(kw)
            domain = []
            student_id = kw.get('student_id')
            if student_id:
                try:
                    domain.append(('student_id', '=', int(student_id)))
                except Exception:
                    pass
            q = (kw.get('q') or '').strip()
            if q:
                domain += ['|',
                           ('student_id.partner_id.name', 'ilike', q),
                           ('product_id.name', 'ilike', q)]
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            items = [_serialize_plan(r) for r in recs]
            return _json_response({
                'items': items, 'data': items,
                'total': total, 'page': page, 'size': limit,
            })
        except Exception as e:
            _logger.exception('list_student_fees')
            return _error_response(str(e), 500)

    @http.route('/api/fees-terms', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_fees_terms(self, **kw):
        try:
            M = request.env['op.fees.terms'].sudo()
            offset, limit, page = _paginate(kw)
            total = M.search_count([])
            recs = M.search([], offset=offset, limit=limit, order='id desc')
            items = [{
                'id': r.id,
                'name': r.name or '',
                'no_days': getattr(r, 'no_days', 0) or 0,
                'line_ids': r.line_ids.ids if hasattr(r, 'line_ids') else [],
            } for r in recs]
            return _json_response({
                'items': items, 'data': items,
                'total': total, 'page': page, 'size': limit,
            })
        except Exception as e:
            _logger.exception('list_fees_terms')
            return _error_response(str(e), 500)
