# -*- coding: utf-8 -*-

from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from markupsafe import Markup
import json
import uuid
import logging

_logger = logging.getLogger(__name__)


class WheelchairAssessment(models.Model):
    _name = 'fusion.wc.assessment'
    _description = 'Wheelchair Assessment & Quotation Builder'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'create_date desc, id desc'
    _rec_name = 'display_name'

    # =========================================================================
    # STATE & IDENTITY
    # =========================================================================
    state = fields.Selection([
        ('draft', 'In Progress'),
        ('review', 'Ready for Review'),
        ('quoted', 'Quotation Generated'),
        ('cancelled', 'Cancelled'),
    ], string='Status', default='draft', tracking=True, index=True)

    display_name = fields.Char(
        string='Display Name',
        compute='_compute_display_name',
        store=True,
    )
    reference = fields.Char(
        string='Reference',
        readonly=True,
        copy=False,
        default=lambda self: _('New'),
    )

    # =========================================================================
    # STEP 1: CLIENT & EQUIPMENT
    # =========================================================================
    partner_id = fields.Many2one('res.partner', string='Client',
        tracking=True, index=True)
    create_new_partner = fields.Boolean(string='New Client', default=False)

    # Client info (for new clients or override)
    client_first_name = fields.Char(string='First Name')
    client_last_name = fields.Char(string='Last Name')
    client_name = fields.Char(string='Client Name',
        compute='_compute_client_name', store=True)
    client_phone = fields.Char(string='Phone')
    client_email = fields.Char(string='Email')
    client_street = fields.Char(string='Street')
    client_street2 = fields.Char(string='Unit/Apt')
    client_city = fields.Char(string='City')
    client_state_id = fields.Many2one('res.country.state', string='Province')
    client_zip = fields.Char(string='Postal Code')
    client_country_id = fields.Many2one('res.country', string='Country')
    client_dob = fields.Date(string='Date of Birth')
    client_health_card = fields.Char(string='Health Card Number')

    equipment_type = fields.Selection(
        selection='_get_equipment_type_selection',
        string='Equipment Type', required=True, tracking=True, index=True)

    @api.model
    def _get_equipment_type_selection(self):
        types = self.env['fusion.equipment.type'].sudo().search([], order='sequence')
        if types:
            return [(t.code, t.name) for t in types]
        return [
            ('manual_wheelchair', 'Manual Wheelchair'),
            ('power_wheelchair', 'Power Wheelchair / Scooter'),
            ('walker', 'Walker / Rollator / Ambulation Aid'),
        ]

    wheelchair_type = fields.Selection([
        ('type_1', 'Type 1 - Standard'),
        ('type_2', 'Type 2 - Lightweight'),
        ('type_3', 'Type 3 - Ultra Lightweight'),
        ('type_4', 'Type 4 - Rigid Frame'),
        ('type_5', 'Type 5 - Dynamic Tilt'),
    ], string='Wheelchair Category')

    powerchair_type = fields.Selection([
        ('type_1', 'Power Base Type 1'),
        ('type_2', 'Power Base Type 2'),
        ('type_3', 'Power Base Type 3'),
        ('scooter', 'Power Scooter'),
    ], string='Power Device Category')

    walker_type = fields.Selection([
        ('adult_walker_1', 'Adult Wheeled Walker Type 1'),
        ('adult_walker_2', 'Adult Wheeled Walker Type 2'),
        ('adult_walker_3', 'Adult Wheeled Walker Type 3'),
        ('paed_walker_1', 'Paediatric Specific Wheeled Walker Type 1'),
        ('paed_walker_2', 'Paediatric Specific Wheeled Walker Type 2'),
        ('paed_walking_frame', 'Paediatric Specific Walking Frame'),
        ('paed_standing_1', 'Paediatric Standing Frame Type 1'),
        ('paed_standing_2', 'Paediatric Standing Frame Type 2'),
        ('forearm_crutches', 'Forearm Crutches'),
        ('stroller', 'Paediatric Specific Specialty Stroller'),
    ], string='Walker / Aid Category')

    client_type = fields.Selection([
        ('reg', 'REG - Regular ADP (75/25)'),
        ('ods', 'ODS - ODSP (100% ADP)'),
        ('acs', 'ACS - ACSD (100% ADP)'),
        ('owp', 'OWP - Ontario Works (100% ADP)'),
        ('ltc', 'LTC - Long Term Care'),
        ('sen', 'SEN - Senior'),
        ('cca', 'CCA - Community Care'),
    ], string='Client Type', default='reg')

    build_type = fields.Selection([
        ('modular', 'Modular'),
        ('custom_fabricated', 'Custom Fabricated'),
    ], string='Build Type', default='modular')

    reason_for_application = fields.Selection([
        ('first_access', 'First Access'),
        ('additions', 'Additions'),
        ('modifications', 'Modifications'),
        ('replacements', 'Replacements'),
    ], string='Reason for Application')

    # Assessment context
    sales_rep_id = fields.Many2one('res.users', string='Sales Rep',
        default=lambda self: self.env.user, tracking=True, index=True)
    authorizer_id = fields.Many2one('res.partner', string='Authorizer/OT',
        tracking=True)
    assessment_date = fields.Datetime(string='Assessment Date',
        default=fields.Datetime.now)

    # =========================================================================
    # STEP 2: ADP PRESCRIPTION MEASUREMENTS
    # =========================================================================
    seat_width = fields.Float(string='Seat Width', digits=(10, 2))
    seat_width_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Seat Width Unit', default='inches')

    seat_depth = fields.Float(string='Seat Depth', digits=(10, 2))
    seat_depth_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Seat Depth Unit', default='inches')

    finished_seat_to_floor_height = fields.Float(
        string='Finished Seat to Floor Height', digits=(10, 2))
    seat_to_floor_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Seat to Floor Unit', default='inches')

    back_cane_height = fields.Float(string='Back Cane Height', digits=(10, 2))
    cane_height_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Cane Height Unit', default='inches')

    finished_back_height = fields.Float(
        string='Finished Back Height', digits=(10, 2))
    back_height_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Back Height Unit', default='inches')

    finished_leg_rest_length = fields.Float(
        string='Finished Leg Rest Length', digits=(10, 2))
    leg_rest_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Leg Rest Unit', default='inches')

    client_weight = fields.Float(string='Client Weight', digits=(10, 1))
    client_weight_unit = fields.Selection([
        ('lbs', 'lbs'), ('kg', 'kg')
    ], string='Weight Unit', default='lbs')

    # =========================================================================
    # WALKER / AMBULATION AID PRESCRIPTION (Section 2a)
    # =========================================================================
    walker_seat_height = fields.Float(string='Seat Height', digits=(10, 2))
    walker_seat_height_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm'), ('na', 'N/A')
    ], string='Seat Height Unit', default='inches')

    push_handle_height = fields.Float(string='Push Handle Height', digits=(10, 2))
    push_handle_height_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Push Handle Height Unit', default='inches')

    hand_grips = fields.Selection([
        ('none', 'None'), ('standard', 'Standard'), ('anatomical', 'Anatomical'),
    ], string='Hand Grips')
    forearm_attachments = fields.Selection([
        ('one', 'One'), ('two', 'Two'),
    ], string='Forearm Attachments')

    width_between_push_handles = fields.Float(
        string='Width Between Push Handles', digits=(10, 2))
    push_handle_width_unit = fields.Selection([
        ('inches', 'Inches'), ('cm', 'cm')
    ], string='Push Handle Width Unit', default='inches')

    walker_brakes = fields.Selection([
        ('none', 'None'), ('push_to_lock', 'Push-To-Lock'),
        ('auto_stop', 'Auto Stop'),
    ], string='Brakes')
    walker_brake_type = fields.Selection([
        ('none', 'None'), ('bilateral', 'Bilateral'),
        ('one_hand', 'One Hand'),
    ], string='Brake Type')
    walker_num_wheels = fields.Selection([
        ('two', 'Two'), ('three', 'Three'), ('four', 'Four'),
    ], string='Number of Wheels')
    walker_wheel_size = fields.Selection([
        ('4_6', '4-6 inches'), ('6_8', '6-8 inches'), ('8_10', '8-10 inches'),
    ], string='Wheel Size')
    walker_back_support = fields.Selection([
        ('yes', 'Yes'), ('no', 'No'),
    ], string='Back Support')

    # Walker ADP options (checkboxes)
    walker_adolescent_wheeled_walker = fields.Boolean(
        string='Adolescent Size Paediatric Specific Wheeled Walker')
    walker_adolescent_walking_frame = fields.Boolean(
        string='Adolescent Size Paediatric Wheeled Walker – Walking Frame')
    walker_adolescent_standing_frame = fields.Boolean(
        string='Adolescent Size Paediatric Standing Frame')

    # =========================================================================
    # POWER BASE / SCOOTER PRESCRIPTION (Section 2c)
    # =========================================================================
    # Note: power bases share seat_width, finished_back_height,
    # finished_seat_to_floor_height, finished_leg_rest_length, seat_depth,
    # and client_weight from the main measurement fields above.
    # Scooters only require client_weight (field 6).

    # Power ADP options (checkboxes from screenshot)
    pw_adjustable_tension_back = fields.Boolean(
        string='Adjustable Tension Back Upholstery')
    pw_midline_control = fields.Boolean(string='Midline Control')
    pw_manual_recline = fields.Boolean(string='Manual Recline Option')
    pw_angle_adjustable_footplates = fields.Boolean(
        string='Angle Adjustable Footplates (pair)')
    pw_manual_elevating_legrests = fields.Boolean(
        string='Manual Elevating Legrests (pair)')
    pw_swingaway_bracket = fields.Boolean(string='Swingaway Mounting Bracket')
    pw_front_riggings = fields.Boolean(string='One Piece 90/90 Front Riggings')
    pw_seat_package_1 = fields.Boolean(
        string='Seat Package 1 for Power Bases',
        help='Includes frame, sling upholstery, armrests, footrests')
    pw_seat_package_2 = fields.Boolean(
        string='Seat Package 2 for Power Bases',
        help='Includes deluxe seat and back, armrests, footrests')
    pw_oxygen_tank = fields.Boolean(string='Oxygen Tank Holder')
    pw_ventilator_tray = fields.Boolean(string='Ventilator Tray')

    # Specialty Components (* require clinical rationale)
    pw_specialty_1_joystick = fields.Boolean(
        string='Specialty Controls 1 Non Standard Joystick*')
    pw_specialty_2_chin = fields.Boolean(
        string='Specialty Controls 2 Chin/Rim Control*')
    pw_specialty_3_touch = fields.Boolean(
        string='Specialty Controls 3 Simple Touch*')
    pw_specialty_4_proximity = fields.Boolean(
        string='Specialty Controls 4 Proximity Control*')
    pw_specialty_5_breath = fields.Boolean(
        string='Specialty Controls 5 Breath Control*')
    pw_specialty_6_scanners = fields.Boolean(
        string='Specialty Controls 6 Scanners*')
    pw_auto_correction = fields.Boolean(string='Auto Correction System*')
    pw_specialty_rationale = fields.Text(
        string='Clinical Rationale for Specialty Components')

    # Power Positioning Devices (require Justification for Funding Chart)
    pw_power_tilt_only = fields.Boolean(string='Power Tilt Only')
    pw_power_recline_only = fields.Boolean(string='Power Recline Only')
    pw_power_tilt_recline = fields.Boolean(string='Power Tilt and Recline')
    pw_power_elevating_footrests = fields.Boolean(
        string='Power Elevating Footrests')
    pw_multi_function_control = fields.Boolean(
        string='Multi-Function Control Box')
    pw_power_add_on = fields.Boolean(string='Power Add-On Device')

    # Computed: convert to inches/lbs for rule evaluation
    seat_width_inches = fields.Float(
        compute='_compute_normalized_measurements', store=True)
    seat_depth_inches = fields.Float(
        compute='_compute_normalized_measurements', store=True)
    back_height_inches = fields.Float(
        compute='_compute_normalized_measurements', store=True)
    client_weight_lbs = fields.Float(
        compute='_compute_normalized_measurements', store=True)

    # =========================================================================
    # STEP 3: FRAME
    # =========================================================================
    frame_product_tmpl_id = fields.Many2one('product.template',
        string='Frame Template')
    frame_product_id = fields.Many2one('product.product',
        string='Wheelchair Frame')
    frame_notes = fields.Text(string='Frame Notes')

    # =========================================================================
    # STEP 4 & 5: SEATING, OPTIONS, ACCESSORIES (via lines)
    # =========================================================================
    line_ids = fields.One2many('fusion.wc.assessment.line', 'assessment_id',
        string='Selected Items')
    upcharge_line_ids = fields.One2many('fusion.wc.assessment.line', 'assessment_id',
        domain=[('is_upcharge', '=', True)], string='Auto-Applied Upcharges')

    # =========================================================================
    # STEP 6: REVIEW & RESULT
    # =========================================================================
    sale_order_id = fields.Many2one('sale.order',
        string='Generated Quotation', readonly=True, copy=False)
    total_estimate = fields.Float(string='Total Estimate',
        compute='_compute_totals', store=True)
    total_adp_estimate = fields.Float(string='Estimated ADP Portion',
        compute='_compute_totals', store=True)
    total_client_estimate = fields.Float(string='Estimated Client Portion',
        compute='_compute_totals', store=True)

    notes = fields.Text(string='Additional Notes')

    # Form state for save/resume
    current_step = fields.Integer(string='Current Step', default=1)
    form_data_json = fields.Text(string='Form State (JSON)',
        help='Stores intermediate form state for save/resume')

    # Generic equipment data (for non-wheelchair equipment types)
    equipment_data_json = fields.Text(string='Equipment Data (JSON)',
        help='JSON storage for equipment-specific field values. '
             'Used by dynamic measurement/custom steps for non-wheelchair types.')

    # =========================================================================
    # SHARING & PUBLIC ACCESS
    # =========================================================================
    access_token = fields.Char(
        string='Access Token', copy=False, index=True,
        help='Token for public access without login')
    portal_url = fields.Char(
        string='Portal URL', compute='_compute_portal_url')
    public_url = fields.Char(
        string='Public URL', compute='_compute_public_url')

    # =========================================================================
    # COMPUTED FIELDS
    # =========================================================================
    @api.depends('client_first_name', 'client_last_name', 'partner_id')
    def _compute_client_name(self):
        for rec in self:
            if rec.partner_id:
                rec.client_name = rec.partner_id.name
            elif rec.client_first_name or rec.client_last_name:
                parts = [rec.client_first_name or '', rec.client_last_name or '']
                rec.client_name = ' '.join(p for p in parts if p)
            else:
                rec.client_name = ''

    @api.depends('reference', 'client_name')
    def _compute_display_name(self):
        for rec in self:
            parts = [rec.reference or _('New')]
            if rec.client_name:
                parts.append(rec.client_name)
            rec.display_name = ' - '.join(parts)

    @api.depends(
        'seat_width', 'seat_width_unit',
        'seat_depth', 'seat_depth_unit',
        'finished_back_height', 'back_height_unit',
        'client_weight', 'client_weight_unit',
    )
    def _compute_normalized_measurements(self):
        """Convert all measurements to inches/lbs for consistent rule evaluation."""
        for rec in self:
            rec.seat_width_inches = (
                rec.seat_width if rec.seat_width_unit == 'inches'
                else rec.seat_width / 2.54
            )
            rec.seat_depth_inches = (
                rec.seat_depth if rec.seat_depth_unit == 'inches'
                else rec.seat_depth / 2.54
            )
            rec.back_height_inches = (
                rec.finished_back_height if rec.back_height_unit == 'inches'
                else rec.finished_back_height / 2.54
            )
            rec.client_weight_lbs = (
                rec.client_weight if rec.client_weight_unit == 'lbs'
                else rec.client_weight * 2.20462
            )

    @api.depends('line_ids.subtotal', 'line_ids.adp_portion', 'line_ids.client_portion')
    def _compute_totals(self):
        for rec in self:
            rec.total_estimate = sum(rec.line_ids.mapped('subtotal'))
            rec.total_adp_estimate = sum(rec.line_ids.mapped('adp_portion'))
            rec.total_client_estimate = sum(rec.line_ids.mapped('client_portion'))

    def _compute_portal_url(self):
        base = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        for rec in self:
            rec.portal_url = f'{base}/my/quotation/builder/{rec.id}/edit' if rec.id else False

    def _compute_public_url(self):
        base = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        for rec in self:
            if rec.access_token:
                rec.public_url = f'{base}/quotation/form/{rec.access_token}'
            else:
                rec.public_url = False

    # =========================================================================
    # SHARING ACTIONS
    # =========================================================================
    def _generate_access_token(self):
        """Generate a unique access token for public sharing."""
        for rec in self:
            if not rec.access_token:
                rec.access_token = uuid.uuid4().hex

    def action_open_portal_form(self):
        """Open the portal assessment form in a new browser tab."""
        self.ensure_one()
        return {
            'type': 'ir.actions.act_url',
            'url': self.portal_url,
            'target': 'new',
        }

    def action_generate_share_link(self):
        """Generate a public access token and show the shareable URL."""
        self.ensure_one()
        self._generate_access_token()
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _('Share Link Generated'),
                'message': _('Public URL copied to clipboard — '
                             'also visible in the Sharing section of this form.'),
                'type': 'info',
                'sticky': False,
            },
        }

    # =========================================================================
    # CRUD OVERRIDES
    # =========================================================================
    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if vals.get('reference', _('New')) == _('New'):
                vals['reference'] = self.env['ir.sequence'].next_by_code(
                    'fusion.wc.assessment') or _('New')
        return super().create(vals_list)

    # =========================================================================
    # UPCHARGE RULE ENGINE
    # =========================================================================
    def _evaluate_upcharge_rules(self):
        """Evaluate all active upcharge rules against current assessment data.
        Creates assessment lines for triggered rules.
        Returns recordset of triggered rules.
        """
        self.ensure_one()

        # Remove previously auto-applied upcharges (allows re-evaluation)
        self.line_ids.filtered(lambda l: l.is_upcharge).unlink()

        rules = self.env['fusion.wc.upcharge.rule'].search([
            ('active', '=', True),
            ('equipment_type', 'in', [self.equipment_type, 'both']),
        ])

        triggered = self.env['fusion.wc.upcharge.rule']
        # Track exclusive groups — only the first matching rule per group applies
        exclusive_triggered = {}

        for rule in rules.sorted('sequence'):
            # Check mutual exclusion
            if rule.mutually_exclusive_group:
                if rule.mutually_exclusive_group in exclusive_triggered:
                    continue

            matched = False

            if rule.trigger_type == 'measurement':
                value = self._get_measurement_value(rule.measurement_field)
                if value and self._compare_values(value, rule.comparison, rule.threshold_value):
                    matched = True

            elif rule.trigger_type == 'weight':
                weight = self.client_weight_lbs
                if weight > rule.weight_min:
                    if not rule.weight_max or weight <= rule.weight_max:
                        matched = True

            elif rule.trigger_type == 'dimension_mismatch':
                val1 = self._get_measurement_value(rule.compare_field_1)
                val2 = self._get_measurement_value(rule.compare_field_2)
                if val1 and val2 and abs(val1 - val2) > 0.01:
                    matched = True

            if matched:
                self._create_upcharge_line(rule)
                triggered |= rule
                if rule.mutually_exclusive_group:
                    exclusive_triggered[rule.mutually_exclusive_group] = rule

        return triggered

    def _get_measurement_value(self, field_name):
        """Map measurement field selection to actual normalized (inches) value."""
        field_map = {
            'seat_width': self.seat_width_inches,
            'seat_depth': self.seat_depth_inches,
            'back_width': self.seat_width_inches,  # backrest width defaults to seat width
            'back_height': self.back_height_inches,
            'seat_to_floor': (
                self.finished_seat_to_floor_height
                if self.seat_to_floor_unit == 'inches'
                else self.finished_seat_to_floor_height / 2.54
            ),
            'leg_rest_length': (
                self.finished_leg_rest_length
                if self.leg_rest_unit == 'inches'
                else self.finished_leg_rest_length / 2.54
            ),
        }
        return field_map.get(field_name, 0.0)

    @staticmethod
    def _compare_values(value, comparison, threshold):
        """Evaluate a comparison between value and threshold."""
        if comparison == 'gt':
            return value > threshold
        elif comparison == 'gte':
            return value >= threshold
        elif comparison == 'lt':
            return value < threshold
        elif comparison == 'eq':
            return abs(value - threshold) < 0.01
        elif comparison == 'neq':
            return abs(value - threshold) >= 0.01
        return False

    def _create_upcharge_line(self, rule):
        """Create an assessment line from an upcharge rule."""
        ADPDevice = self.env['fusion.adp.device.code'].sudo()
        adp_device = ADPDevice.search(
            [('device_code', '=', rule.adp_device_code), ('active', '=', True)],
            limit=1
        )

        vals = {
            'assessment_id': self.id,
            'product_id': rule.product_id.id if rule.product_id else False,
            'product_name': (
                adp_device.device_description
                if adp_device else rule.name
            ),
            'quantity': 1,
            'adp_device_code': rule.adp_device_code,
            'adp_price': adp_device.adp_price if adp_device else 0,
            'unit_price': adp_device.adp_price if adp_device else 0,
            'is_upcharge': True,
            'upcharge_rule_id': rule.id,
            'upcharge_reason': rule.description or rule.name,
        }
        return self.env['fusion.wc.assessment.line'].create(vals)

    # =========================================================================
    # QUOTATION GENERATION
    # =========================================================================
    def action_generate_quotation(self):
        """Generate a draft sale.order from this assessment."""
        self.ensure_one()
        if self.sale_order_id:
            raise UserError(_('A quotation has already been generated for this assessment.'))

        # Run upcharge engine first
        triggered_rules = self._evaluate_upcharge_rules()

        # Resolve or create partner
        partner = self._resolve_partner()

        # Add frame as an assessment line if not already present
        self._ensure_frame_line()

        # Create sale order
        so_vals = self._prepare_sale_order_vals(partner)
        sale_order = self.env['sale.order'].sudo().create(so_vals)

        # Create sale order lines from assessment lines
        for line in self.line_ids:
            sol_vals = self._prepare_sale_order_line_vals(sale_order, line)
            self.env['sale.order.line'].sudo().create(sol_vals)

        # Update assessment
        self.write({
            'sale_order_id': sale_order.id,
            'state': 'quoted',
        })

        # Post chatter message
        self._post_quotation_message(sale_order, triggered_rules)

        return {
            'type': 'ir.actions.act_window',
            'res_model': 'sale.order',
            'res_id': sale_order.id,
            'view_mode': 'form',
            'target': 'current',
        }

    def _resolve_partner(self):
        """Get existing partner or create a new one."""
        self.ensure_one()
        if self.partner_id:
            return self.partner_id

        if not self.client_first_name and not self.client_last_name:
            raise UserError(_('Please select an existing client or provide client name.'))

        partner_vals = {
            'name': self.client_name,
            'phone': self.client_phone,
            'email': self.client_email,
            'street': self.client_street,
            'street2': self.client_street2,
            'city': self.client_city,
            'state_id': self.client_state_id.id if self.client_state_id else False,
            'zip': self.client_zip,
            'country_id': self.client_country_id.id if self.client_country_id else False,
            'customer_rank': 1,
        }
        partner = self.env['res.partner'].sudo().create(partner_vals)
        self.partner_id = partner
        return partner

    def _ensure_frame_line(self):
        """Add the frame product as an assessment line if not already present."""
        self.ensure_one()

        # If we have a template but no resolved variant, resolve to first variant
        if not self.frame_product_id and self.frame_product_tmpl_id:
            variant = self.frame_product_tmpl_id.product_variant_ids[:1]
            if variant:
                self.frame_product_id = variant.id

        if not self.frame_product_id:
            return

        # Pick the right frame section based on equipment type
        frame_code_map = {
            'manual_wheelchair': 'mw_frame',
            'power_wheelchair': 'pw_frame',
            'walker': 'walker_frame',
        }
        code = frame_code_map.get(self.equipment_type, 'mw_frame')
        frame_section = self.env['fusion.wc.section'].search(
            [('code', '=', code)], limit=1)
        if not frame_section:
            # Fall back to legacy code
            frame_section = self.env['fusion.wc.section'].search(
                [('code', '=', 'frame')], limit=1)

        existing = self.line_ids.filtered(
            lambda l: l.product_id == self.frame_product_id and not l.is_upcharge)
        if existing:
            return

        # Build description from measurements
        desc_parts = [self.frame_product_id.display_name]
        if self.seat_width:
            desc_parts.append(f"Seat Width: {self.seat_width}{self.seat_width_unit}")
        if self.seat_depth:
            desc_parts.append(f"Seat Depth: {self.seat_depth}{self.seat_depth_unit}")
        if self.finished_seat_to_floor_height:
            desc_parts.append(
                f"Seat to Floor: {self.finished_seat_to_floor_height}"
                f"{self.seat_to_floor_unit}")
        if self.finished_leg_rest_length:
            desc_parts.append(
                f"Leg Rest: {self.finished_leg_rest_length}{self.leg_rest_unit}")
        if self.back_cane_height:
            desc_parts.append(
                f"Cane Height: {self.back_cane_height}{self.cane_height_unit}")

        product = self.frame_product_id
        adp_code = product.product_tmpl_id.x_fc_adp_device_code or ''
        adp_price = product.product_tmpl_id.x_fc_adp_price or product.lst_price

        self.env['fusion.wc.assessment.line'].create({
            'assessment_id': self.id,
            'section_id': frame_section.id if frame_section else False,
            'product_id': product.id,
            'product_name': '\n'.join(desc_parts),
            'quantity': 1,
            'adp_device_code': adp_code,
            'adp_price': adp_price,
            'unit_price': product.lst_price,
        })

    def _prepare_sale_order_vals(self, partner):
        """Prepare values for sale.order creation."""
        self.ensure_one()

        # Map client_type to fusion_claims x_fc_client_type
        client_type_map = {
            'reg': 'REG',
            'ods': 'ODS',
            'acs': 'ACS',
            'owp': 'OWP',
            'ltc': 'LTC',
            'sen': 'SEN',
            'cca': 'CCA',
        }

        vals = {
            'partner_id': partner.id,
            'user_id': self.sales_rep_id.id,
            'state': 'draft',
            'origin': f'WC Assessment: {self.reference}',
            'wc_assessment_id': self.id,
        }

        # Set fusion_claims fields if available
        if hasattr(self.env['sale.order'], 'x_fc_sale_type'):
            vals['x_fc_sale_type'] = 'adp'
        if hasattr(self.env['sale.order'], 'x_fc_client_type'):
            vals['x_fc_client_type'] = client_type_map.get(self.client_type, 'REG')
        if hasattr(self.env['sale.order'], 'x_fc_adp_application_status'):
            vals['x_fc_adp_application_status'] = 'quotation'
        if self.authorizer_id and hasattr(self.env['sale.order'], 'x_fc_authorizer_id'):
            vals['x_fc_authorizer_id'] = self.authorizer_id.id

        return vals

    def _prepare_sale_order_line_vals(self, sale_order, line):
        """Prepare values for sale.order.line creation from assessment line."""
        vals = {
            'order_id': sale_order.id,
            'product_id': line.product_id.id if line.product_id else False,
            'name': line.product_name or (
                line.product_id.display_name if line.product_id else line.adp_device_code
            ),
            'product_uom_qty': line.quantity,
            'price_unit': line.unit_price or (
                line.product_id.lst_price if line.product_id else 0
            ),
        }
        return vals

    def _post_quotation_message(self, sale_order, triggered_rules):
        """Post a chatter message summarizing the assessment."""
        self.ensure_one()

        lines_html = ''
        for line in self.line_ids:
            icon = '&#x26A1;' if line.is_upcharge else '&#x2705;'
            lines_html += (
                f'<li>{icon} {line.product_name or line.adp_device_code} '
                f'x{line.quantity} — ${line.unit_price:.2f}</li>'
            )

        measurements_html = ''
        if self.seat_width:
            measurements_html += (
                f'<li>Seat Width: {self.seat_width} {self.seat_width_unit}</li>')
        if self.seat_depth:
            measurements_html += (
                f'<li>Seat Depth: {self.seat_depth} {self.seat_depth_unit}</li>')
        if self.finished_seat_to_floor_height:
            measurements_html += (
                f'<li>Seat to Floor: {self.finished_seat_to_floor_height} '
                f'{self.seat_to_floor_unit}</li>')
        if self.back_cane_height:
            measurements_html += (
                f'<li>Back Cane Height: {self.back_cane_height} '
                f'{self.cane_height_unit}</li>')
        if self.finished_back_height:
            measurements_html += (
                f'<li>Finished Back Height: {self.finished_back_height} '
                f'{self.back_height_unit}</li>')
        if self.finished_leg_rest_length:
            measurements_html += (
                f'<li>Leg Rest Length: {self.finished_leg_rest_length} '
                f'{self.leg_rest_unit}</li>')
        if self.client_weight:
            measurements_html += (
                f'<li>Client Weight: {self.client_weight} {self.client_weight_unit}</li>')

        upcharges_html = ''
        if triggered_rules:
            upcharges_html = '<h4>Auto-Applied Upcharges</h4><ul>'
            for rule in triggered_rules:
                upcharges_html += (
                    f'<li>&#x26A1; {rule.name} ({rule.adp_device_code})</li>')
            upcharges_html += '</ul>'

        body = Markup(
            f'<h3>Wheelchair Assessment Completed</h3>'
            f'<p>Assessment: {self.reference}<br/>'
            f'Equipment: {dict(self._fields["equipment_type"].selection).get(self.equipment_type, "")}<br/>'
            f'Build Type: {dict(self._fields["build_type"].selection).get(self.build_type, "")}</p>'
            f'<h4>Measurements</h4><ul>{measurements_html}</ul>'
            f'<h4>Selected Items</h4><ul>{lines_html}</ul>'
            f'{upcharges_html}'
        )

        sale_order.message_post(body=body, message_type='comment',
            subtype_xmlid='mail.mt_note')

    # =========================================================================
    # ACTIONS
    # =========================================================================
    def action_mark_review(self):
        """Mark assessment as ready for review."""
        for rec in self:
            rec.state = 'review'

    def action_cancel(self):
        """Cancel the assessment."""
        for rec in self:
            rec.state = 'cancelled'

    def action_reset_draft(self):
        """Reset to draft."""
        for rec in self:
            if rec.state == 'cancelled':
                rec.state = 'draft'

    def action_view_quotation(self):
        """Open the generated quotation."""
        self.ensure_one()
        if not self.sale_order_id:
            raise UserError(_('No quotation has been generated yet.'))
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'sale.order',
            'res_id': self.sale_order_id.id,
            'view_mode': 'form',
            'target': 'current',
        }
