# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)

import logging
import xmlrpc.client
from odoo import models, fields, api
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class FusionSyncConfig(models.Model):
    _name = 'fusion.sync.config'
    _description = 'Inventory Sync Configuration'
    _rec_name = 'name'

    name = fields.Char(string='Connection Name', required=True)
    url = fields.Char(string='Remote URL', required=True,
                      help='Full URL of the remote Odoo instance (e.g., https://erp.mobilityspecialties.com)')
    db_name = fields.Char(string='Database Name', required=True,
                          help='Name of the remote database')
    username = fields.Char(string='Username', required=True,
                           help='Login username for the remote instance')
    api_key = fields.Char(string='API Key / Password', required=True,
                          help='API key or password for authentication')
    active = fields.Boolean(default=True)
    state = fields.Selection([
        ('draft', 'Not Connected'),
        ('connected', 'Connected'),
        ('error', 'Connection Error'),
    ], string='Status', default='draft', readonly=True)
    last_sync = fields.Datetime(string='Last Sync', readonly=True)
    last_sync_status = fields.Text(string='Last Sync Result', readonly=True)
    sync_interval = fields.Integer(string='Sync Interval (minutes)', default=30,
                                   help='How often to run the automatic sync')
    remote_uid = fields.Integer(string='Remote User ID', readonly=True)
    company_id = fields.Many2one('res.company', string='Company',
                                 default=lambda self: self.env.company)

    sync_products = fields.Boolean(string='Sync Products', default=True)
    sync_stock = fields.Boolean(string='Sync Stock Levels', default=True)
    remote_warehouse_name = fields.Char(
        string='Remote Warehouse Name',
        help='Name of the warehouse on the remote instance to read stock from. Leave empty for all.')

    is_shared_warehouse = fields.Boolean(
        string='Shared Warehouse',
        help='Enable shared warehouse mode for cross-company inventory management')
    warehouse_location_id = fields.Many2one(
        'stock.location', string='Shared Warehouse Location',
        help='The stock location representing the shared warehouse')

    remote_partner_id = fields.Many2one(
        'res.partner', string='Remote Company (Partner)',
        help='The local partner record that represents the remote company. '
             'Used as vendor when creating local POs for inter-company transfers.')
    local_company_name = fields.Char(
        string='This Company on Remote',
        help='The name of this company as it appears on the remote instance. '
             'Used to find the correct partner when creating SOs on the remote side.')

    sync_warehouse_ids = fields.One2many(
        'fusion.sync.warehouse', 'config_id', string='Remote Warehouses')
    sync_warehouse_count = fields.Integer(
        compute='_compute_sync_warehouse_count')

    @api.depends('sync_warehouse_ids')
    def _compute_sync_warehouse_count(self):
        for rec in self:
            rec.sync_warehouse_count = len(rec.sync_warehouse_ids)

    # ── XML-RPC Connection ──

    def _get_xmlrpc_connection(self):
        self.ensure_one()
        url = self.url.rstrip('/')
        try:
            common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common', allow_none=True)
            uid = common.authenticate(self.db_name, self.username, self.api_key, {})
            if not uid:
                raise UserError('Authentication failed. Check username/API key.')
            models_proxy = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object', allow_none=True)
            return uid, models_proxy
        except xmlrpc.client.Fault as e:
            raise UserError(f'XML-RPC error: {e.faultString}')
        except Exception as e:
            raise UserError(f'Connection error: {str(e)}')

    # ── Actions ──

    def action_test_connection(self):
        self.ensure_one()
        try:
            uid, models_proxy = self._get_xmlrpc_connection()
            version_info = xmlrpc.client.ServerProxy(
                f'{self.url.rstrip("/")}/xmlrpc/2/common', allow_none=True
            ).version()
            self.write({
                'state': 'connected',
                'remote_uid': uid,
                'last_sync_status': f'Connection successful. Remote server: {version_info.get("server_serie", "unknown")}',
            })
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': 'Connection Successful',
                    'message': f'Connected to {self.url} as user ID {uid}',
                    'type': 'success',
                    'sticky': False,
                },
            }
        except Exception as e:
            self.write({
                'state': 'error',
                'last_sync_status': f'Connection failed: {str(e)}',
            })
            raise

    def action_sync_now(self):
        self.ensure_one()
        self._run_sync()
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'Sync Complete',
                'message': self.last_sync_status,
                'type': 'success',
                'sticky': False,
            },
        }

    # ── Core Sync Logic ──

    def _run_sync(self):
        self.ensure_one()
        try:
            uid, models_proxy = self._get_xmlrpc_connection()
            results = []

            wh_count = self._sync_warehouses(uid, models_proxy)
            results.append(f'{wh_count} warehouses discovered')

            if self.sync_products:
                count = self._sync_products(uid, models_proxy)
                results.append(f'{count} products synced')

            if self.sync_stock:
                count = self._sync_stock_levels(uid, models_proxy)
                results.append(f'{count} stock levels updated')

            status = ' | '.join(results)
            self.write({
                'state': 'connected',
                'last_sync': fields.Datetime.now(),
                'last_sync_status': status,
            })

            self.env['fusion.sync.log'].create({
                'config_id': self.id,
                'direction': 'pull',
                'sync_type': 'full',
                'status': 'success',
                'summary': status,
                'product_count': sum(1 for r in results if 'product' in r.lower()),
            })
            _logger.info('Inventory sync complete for %s: %s', self.name, status)

        except Exception as e:
            error_msg = f'Sync failed: {str(e)}'
            self.write({
                'state': 'error',
                'last_sync': fields.Datetime.now(),
                'last_sync_status': error_msg,
            })
            self.env['fusion.sync.log'].create({
                'config_id': self.id,
                'direction': 'pull',
                'sync_type': 'full',
                'status': 'error',
                'summary': error_msg,
            })
            _logger.error('Sync failed for %s: %s', self.name, error_msg)

    def _sync_warehouses(self, uid, models_proxy):
        """Discover and store remote warehouse metadata.
        Only syncs warehouses belonging to the remote user's main company."""
        self.ensure_one()
        SyncWH = self.env['fusion.sync.warehouse']

        user_data = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'res.users', 'read', [[uid]],
            {'fields': ['company_id']}
        )
        wh_domain = []
        if user_data and user_data[0].get('company_id'):
            remote_company_id = user_data[0]['company_id']
            if isinstance(remote_company_id, (list, tuple)):
                remote_company_id = remote_company_id[0]
            wh_domain = [('company_id', '=', remote_company_id)]

        remote_warehouses = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'stock.warehouse', 'search_read',
            [wh_domain],
            {'fields': ['id', 'name', 'code', 'company_id', 'lot_stock_id']}
        )

        seen_ids = set()
        for rwh in remote_warehouses:
            wh_id = rwh['id']
            seen_ids.add(wh_id)
            company = rwh.get('company_id')
            company_name = company[1] if isinstance(company, (list, tuple)) else ''
            lot_stock = rwh.get('lot_stock_id')
            lot_stock_id = lot_stock[0] if isinstance(lot_stock, (list, tuple)) else (lot_stock or 0)

            existing = SyncWH.search([
                ('config_id', '=', self.id),
                ('remote_warehouse_id', '=', wh_id),
            ], limit=1)

            vals = {
                'config_id': self.id,
                'remote_warehouse_id': wh_id,
                'remote_lot_stock_id': lot_stock_id,
                'name': rwh.get('name', ''),
                'code': rwh.get('code', ''),
                'company_name': company_name,
            }

            if existing:
                existing.write(vals)
            else:
                SyncWH.create(vals)

        stale = SyncWH.search([
            ('config_id', '=', self.id),
            ('remote_warehouse_id', 'not in', list(seen_ids)),
        ])
        if stale:
            stale.write({'active': False})

        return len(remote_warehouses)

    def _sync_products(self, uid, models_proxy):
        """Pull remote product catalog and auto-match to local products."""
        self.ensure_one()
        Mapping = self.env['fusion.product.sync.mapping']

        remote_products = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'product.template', 'search_read',
            [[('type', 'in', ['consu', 'product'])]],
            {'fields': ['id', 'name', 'default_code', 'barcode',
                        'list_price', 'type', 'categ_id'],
             'limit': 10000}
        )

        synced = 0
        for rp in remote_products:
            mapping = Mapping.search([
                ('config_id', '=', self.id),
                ('remote_product_id', '=', rp['id']),
            ], limit=1)

            vals = {
                'config_id': self.id,
                'remote_product_id': rp['id'],
                'remote_product_name': rp.get('name', ''),
                'remote_default_code': rp.get('default_code', '') or '',
                'remote_barcode': rp.get('barcode', '') or '',
                'remote_list_price': rp.get('list_price', 0),
                'remote_category': (
                    rp['categ_id'][1]
                    if isinstance(rp.get('categ_id'), (list, tuple))
                    else ''),
            }

            if mapping:
                mapping.write(vals)
            else:
                local_product = self._find_local_product(rp)
                vals['local_product_id'] = local_product.id if local_product else False
                vals['auto_matched'] = bool(local_product)
                Mapping.create(vals)

            synced += 1

        return synced

    def _find_local_product(self, remote_product):
        """Match a remote product to a local one by SKU, barcode, then name."""
        Template = self.env['product.template']
        code = remote_product.get('default_code')
        if code:
            match = Template.search([('default_code', '=', code)], limit=1)
            if match:
                return match

        barcode = remote_product.get('barcode')
        if barcode:
            match = Template.search([('barcode', '=', barcode)], limit=1)
            if match:
                return match

        name = remote_product.get('name')
        if name:
            match = Template.search([('name', '=ilike', name)], limit=1)
            if match:
                return match

        return False

    def _sync_stock_levels(self, uid, models_proxy):
        """Pull per-warehouse stock levels and store in fusion.sync.stock."""
        self.ensure_one()
        Mapping = self.env['fusion.product.sync.mapping']
        SyncStock = self.env['fusion.sync.stock']

        warehouses = self.sync_warehouse_ids.filtered('active')
        if not warehouses:
            _logger.info('No remote warehouses found for %s, skipping stock sync', self.name)
            return 0

        mappings = Mapping.search([
            ('config_id', '=', self.id),
            ('remote_product_id', '!=', 0),
        ])
        if not mappings:
            return 0

        remote_tmpl_ids = [m.remote_product_id for m in mappings]
        mapping_by_remote = {m.remote_product_id: m for m in mappings}

        total_updated = 0
        now = fields.Datetime.now()

        for wh in warehouses:
            if not wh.remote_lot_stock_id:
                continue

            try:
                remote_quants = models_proxy.execute_kw(
                    self.db_name, uid, self.api_key,
                    'stock.quant', 'search_read',
                    [[
                        ('location_id', 'child_of', wh.remote_lot_stock_id),
                        ('product_id.product_tmpl_id', 'in', remote_tmpl_ids),
                        ('quantity', '!=', 0),
                    ]],
                    {'fields': ['product_id', 'quantity', 'reserved_quantity']}
                )
            except Exception as e:
                _logger.warning(
                    'Stock sync failed for warehouse %s: %s', wh.name, e)
                continue

            stock_by_tmpl = {}
            product_tmpl_cache = {}

            product_ids_needed = set()
            for q in remote_quants:
                pid = q['product_id']
                if isinstance(pid, (list, tuple)):
                    pid = pid[0]
                product_ids_needed.add(pid)

            if product_ids_needed:
                remote_variants = models_proxy.execute_kw(
                    self.db_name, uid, self.api_key,
                    'product.product', 'search_read',
                    [[('id', 'in', list(product_ids_needed))]],
                    {'fields': ['id', 'product_tmpl_id']}
                )
                for rv in remote_variants:
                    tmpl = rv['product_tmpl_id']
                    tmpl_id = tmpl[0] if isinstance(tmpl, (list, tuple)) else tmpl
                    product_tmpl_cache[rv['id']] = tmpl_id

            for q in remote_quants:
                pid = q['product_id']
                if isinstance(pid, (list, tuple)):
                    pid = pid[0]
                tmpl_id = product_tmpl_cache.get(pid)
                if not tmpl_id:
                    continue
                if tmpl_id not in stock_by_tmpl:
                    stock_by_tmpl[tmpl_id] = {'qty': 0.0, 'reserved': 0.0}
                stock_by_tmpl[tmpl_id]['qty'] += q.get('quantity', 0)
                stock_by_tmpl[tmpl_id]['reserved'] += q.get('reserved_quantity', 0)

            for tmpl_id, stock in stock_by_tmpl.items():
                mapping = mapping_by_remote.get(tmpl_id)
                if not mapping:
                    continue

                qty_available = stock['qty'] - stock['reserved']
                qty_forecast = stock['qty']

                existing = SyncStock.search([
                    ('mapping_id', '=', mapping.id),
                    ('sync_warehouse_id', '=', wh.id),
                ], limit=1)

                vals = {
                    'mapping_id': mapping.id,
                    'sync_warehouse_id': wh.id,
                    'qty_available': qty_available,
                    'qty_forecast': qty_forecast,
                    'last_sync': now,
                }

                if existing:
                    existing.write(vals)
                else:
                    SyncStock.create(vals)
                total_updated += 1

            zero_mappings = set(mapping_by_remote.keys()) - set(stock_by_tmpl.keys())
            if zero_mappings:
                for tmpl_id in zero_mappings:
                    mapping = mapping_by_remote.get(tmpl_id)
                    if not mapping:
                        continue
                    existing = SyncStock.search([
                        ('mapping_id', '=', mapping.id),
                        ('sync_warehouse_id', '=', wh.id),
                    ], limit=1)
                    if existing and existing.qty_available != 0:
                        existing.write({
                            'qty_available': 0,
                            'qty_forecast': 0,
                            'last_sync': now,
                        })

        for mapping in mappings:
            mapping.last_stock_sync = now

        return total_updated

    def _sync_stock_for_products(self, product_tmpl_ids):
        """Targeted stock re-sync for specific products (called after stock moves)."""
        self.ensure_one()
        if not product_tmpl_ids:
            return

        Mapping = self.env['fusion.product.sync.mapping']
        mappings = Mapping.search([
            ('config_id', '=', self.id),
            ('local_product_id', 'in', product_tmpl_ids),
            ('remote_product_id', '!=', 0),
        ])
        if not mappings:
            return

        try:
            uid, models_proxy = self._get_xmlrpc_connection()
        except Exception:
            return

        warehouses = self.sync_warehouse_ids.filtered('active')
        SyncStock = self.env['fusion.sync.stock']
        now = fields.Datetime.now()

        remote_tmpl_ids = [m.remote_product_id for m in mappings]
        mapping_by_remote = {m.remote_product_id: m for m in mappings}

        for wh in warehouses:
            if not wh.remote_lot_stock_id:
                continue
            try:
                remote_quants = models_proxy.execute_kw(
                    self.db_name, uid, self.api_key,
                    'stock.quant', 'search_read',
                    [[
                        ('location_id', 'child_of', wh.remote_lot_stock_id),
                        ('product_id.product_tmpl_id', 'in', remote_tmpl_ids),
                    ]],
                    {'fields': ['product_id', 'quantity', 'reserved_quantity']}
                )
            except Exception:
                continue

            stock_by_tmpl = {}
            product_ids_needed = set()
            for q in remote_quants:
                pid = q['product_id']
                if isinstance(pid, (list, tuple)):
                    pid = pid[0]
                product_ids_needed.add(pid)

            product_tmpl_cache = {}
            if product_ids_needed:
                remote_variants = models_proxy.execute_kw(
                    self.db_name, uid, self.api_key,
                    'product.product', 'search_read',
                    [[('id', 'in', list(product_ids_needed))]],
                    {'fields': ['id', 'product_tmpl_id']}
                )
                for rv in remote_variants:
                    tmpl = rv['product_tmpl_id']
                    product_tmpl_cache[rv['id']] = (
                        tmpl[0] if isinstance(tmpl, (list, tuple)) else tmpl)

            for q in remote_quants:
                pid = q['product_id']
                if isinstance(pid, (list, tuple)):
                    pid = pid[0]
                tmpl_id = product_tmpl_cache.get(pid)
                if not tmpl_id:
                    continue
                if tmpl_id not in stock_by_tmpl:
                    stock_by_tmpl[tmpl_id] = {'qty': 0.0, 'reserved': 0.0}
                stock_by_tmpl[tmpl_id]['qty'] += q.get('quantity', 0)
                stock_by_tmpl[tmpl_id]['reserved'] += q.get('reserved_quantity', 0)

            for tmpl_id in remote_tmpl_ids:
                mapping = mapping_by_remote.get(tmpl_id)
                if not mapping:
                    continue
                stock = stock_by_tmpl.get(tmpl_id, {'qty': 0, 'reserved': 0})
                existing = SyncStock.search([
                    ('mapping_id', '=', mapping.id),
                    ('sync_warehouse_id', '=', wh.id),
                ], limit=1)
                vals = {
                    'mapping_id': mapping.id,
                    'sync_warehouse_id': wh.id,
                    'qty_available': stock['qty'] - stock['reserved'],
                    'qty_forecast': stock['qty'],
                    'last_sync': now,
                }
                if existing:
                    existing.write(vals)
                else:
                    SyncStock.create(vals)

    # ── Inter-Company Transfer Helpers ──

    def _create_remote_sale_order(self, product_mapping, qty, partner_name):
        """Create a sale order on the remote instance for inter-company transfers."""
        self.ensure_one()
        uid, models_proxy = self._get_xmlrpc_connection()

        partners = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'res.partner', 'search_read',
            [[('name', 'ilike', partner_name)]],
            {'fields': ['id', 'name'], 'limit': 1}
        )
        if not partners:
            raise UserError(f'Partner "{partner_name}" not found on remote instance.')

        remote_product_ids = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'product.product', 'search',
            [[('product_tmpl_id', '=', product_mapping.remote_product_id)]],
            {'limit': 1}
        )
        if not remote_product_ids:
            raise UserError('Remote product variant not found.')

        so_vals = {
            'partner_id': partners[0]['id'],
            'order_line': [(0, 0, {
                'product_id': remote_product_ids[0],
                'product_uom_qty': qty,
            })],
        }

        remote_so_id = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'sale.order', 'create', [so_vals]
        )

        models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'sale.order', 'action_confirm', [[remote_so_id]]
        )

        so_data = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'sale.order', 'read', [[remote_so_id]], {'fields': ['name']}
        )
        so_name = so_data[0]['name'] if so_data else ''

        return remote_so_id, so_name

    def _create_remote_invoice(self, remote_so_id):
        """Create and post an invoice for a remote sale order."""
        self.ensure_one()
        uid, models_proxy = self._get_xmlrpc_connection()

        models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'sale.order', 'action_create_invoices', [[remote_so_id]]
        )

        so_data = models_proxy.execute_kw(
            self.db_name, uid, self.api_key,
            'sale.order', 'read', [[remote_so_id]],
            {'fields': ['invoice_ids']}
        )
        invoice_ids = so_data[0].get('invoice_ids', []) if so_data else []

        if invoice_ids:
            inv_data = models_proxy.execute_kw(
                self.db_name, uid, self.api_key,
                'account.move', 'read',
                [invoice_ids],
                {'fields': ['id', 'name', 'amount_total']}
            )
            return inv_data[0]['id'] if inv_data else False

        return False

    # ── Cron ──

    @api.model
    def _cron_sync_inventory(self):
        configs = self.search([('active', '=', True), ('state', '!=', 'draft')])
        for config in configs:
            try:
                config._run_sync()
            except Exception as e:
                _logger.error('Cron sync failed for %s: %s', config.name, e)
