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, _get_json_body, _paginate,
)

_logger = logging.getLogger(__name__)


def _detect_user_type(u):
    """Detect effective user type from user_type field, groups, and linked records."""
    ut = getattr(u, 'user_type', '') or ''
    if ut == 'admin':
        return 'admin'
    if ut == 'teacher':
        return 'teacher'
    if u.has_group('base.group_system') or u.has_group('base.group_erp_manager'):
        return 'admin'
    is_faculty = bool(u.env['op.faculty'].sudo().search([('user_id', '=', u.id)], limit=1))
    if is_faculty:
        return 'teacher'
    is_student = bool(u.env['op.student'].sudo().search([('user_id', '=', u.id)], limit=1))
    if is_student:
        return 'student'
    if u.has_group('base.group_portal'):
        return 'student'
    return ut or 'user'


def _ser_user(u):
    role_ids = u.role_ids.ids if hasattr(u, 'role_ids') else []
    roles = [{'id': r.id, 'name': r.name or ''} for r in u.role_ids] if hasattr(u, 'role_ids') else []
    return {
        'id': u.id,
        'name': u.name or '',
        'email': u.email or u.login or '',
        'login': u.login or '',
        'user_type': _detect_user_type(u),
        'is_verified': True,
        'active': u.active,
        'phone': u.phone or '',
        'gender': getattr(u, 'gender', '') or '',
        'role_ids': role_ids,
        'roles': roles,
        'effective_permission_count': len(u.get_all_permissions()) if hasattr(u, 'get_all_permissions') else 0,
    }


def _ser_role(r):
    """Full role serialization for the frontend Role type."""
    entity = r.entity_id
    return {
        'id': r.id,
        'name': r.name or '',
        'code': getattr(r, 'code', '') or '',
        'description': getattr(r, 'description', '') or '',
        'entity_id': entity.id if entity else None,
        'entity_name': entity.name if entity else '',
        'permission_ids': r.permission_ids.ids,
        'permissions': [
            {
                'id': p.id,
                'name': getattr(p, 'name', '') or p.code or '',
                'code': p.code or '',
                'description': getattr(p, 'description', '') or '',
                'category': getattr(p, 'topic', '') or '',
            }
            for p in r.permission_ids
        ],
        'user_count': getattr(r, 'user_count', 0) or len(r.user_ids) if hasattr(r, 'user_ids') else 0,
    }


def _ser_perm(p):
    return {
        'id': p.id,
        'name': getattr(p, 'name', '') or p.code or '',
        'code': p.code or '',
        'description': getattr(p, 'description', '') or '',
        'category': getattr(p, 'topic', '') or '',
    }


class UsersRolesController(http.Controller):

    # ── Users ────────────────────────────────────────────────────────

    @http.route('/api/users/list', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_users(self, **kw):
        try:
            M = request.env['res.users'].sudo()
            domain = [('share', '=', False)]
            if kw.get('type'):
                domain.append(('user_type', '=', kw['type']))
            if kw.get('search'):
                domain.append(('name', 'ilike', kw['search']))
            offset, limit, page = _paginate(kw)
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            items = [_ser_user(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_users')
            return _error_response(str(e), 500)

    @http.route('/api/users/<int:uid_param>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_user(self, uid_param, **kw):
        try:
            rec = request.env['res.users'].sudo().browse(uid_param)
            if not rec.exists():
                return _error_response('Not found', 404)
            return _json_response({'data': _ser_user(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/users/update', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_user(self, **kw):
        try:
            body = _get_json_body()
            uid_val = body.get('id')
            if not uid_val:
                return _error_response('Missing id', 400)
            rec = request.env['res.users'].sudo().browse(int(uid_val))
            if not rec.exists():
                return _error_response('Not found', 404)
            vals = {}
            if 'name' in body:
                vals['name'] = body['name']
            if 'email' in body:
                vals['email'] = body['email']
            if 'phone' in body:
                vals['phone'] = body['phone']
            if vals:
                rec.write(vals)
            return _json_response({'data': _ser_user(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/users/create', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_user(self, **kw):
        try:
            body = _get_json_body()
            vals = {
                'name': body.get('name', ''),
                'login': body.get('email', body.get('login', '')),
                'email': body.get('email', ''),
                'password': body.get('password', 'user123'),
            }
            rec = request.env['res.users'].sudo().create(vals)
            return _json_response({'data': _ser_user(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/batch_users', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_batch_users(self, **kw):
        try:
            body = _get_json_body()
            users = body.get('users', [])
            created = []
            for u in users:
                vals = {
                    'name': u.get('name', ''),
                    'login': u.get('email', u.get('login', '')),
                    'email': u.get('email', ''),
                    'password': u.get('password', 'user123'),
                }
                rec = request.env['res.users'].sudo().create(vals)
                created.append(rec.id)
            return _json_response({'success': True, 'created_ids': created})
        except Exception as e:
            return _error_response(str(e), 500)

    # ── Roles ────────────────────────────────────────────────────────

    @http.route('/api/roles', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_roles(self, **kw):
        try:
            M = request.env['encoach.role'].sudo()
            domain = []
            if kw.get('search'):
                domain.append(('name', 'ilike', kw['search']))
            if kw.get('entity_id'):
                domain.append(('entity_id', '=', int(kw['entity_id'])))
            offset, limit, page = _paginate(kw)
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            items = [_ser_role(r) for r in recs]
            return _json_response({'data': items, 'total': total, 'page': page, 'size': limit})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/roles/<int:rid>', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_role(self, rid, **kw):
        try:
            rec = request.env['encoach.role'].sudo().browse(rid)
            if not rec.exists():
                return _error_response('Not found', 404)
            return _json_response({'data': _ser_role(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/roles', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_role(self, **kw):
        try:
            body = _get_json_body()
            name = body.get('name', '').strip()
            if not name:
                return _error_response('Name is required', 400)

            entity_id = body.get('entity_id')
            if not entity_id:
                entity = request.env['encoach.entity'].sudo().search([], limit=1)
                entity_id = entity.id if entity else False
            if not entity_id:
                return _error_response('No entity available for role', 400)

            vals = {
                'name': name,
                'entity_id': int(entity_id),
            }
            if body.get('code'):
                vals['code'] = body['code']
            if body.get('description'):
                vals['description'] = body['description']
            if body.get('permission_ids'):
                vals['permission_ids'] = [(6, 0, [int(p) for p in body['permission_ids']])]

            rec = request.env['encoach.role'].sudo().create(vals)
            return _json_response({'data': _ser_role(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/roles/<int:rid>', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_role(self, rid, **kw):
        try:
            rec = request.env['encoach.role'].sudo().browse(rid)
            if not rec.exists():
                return _error_response('Not found', 404)
            body = _get_json_body()
            vals = {}
            if 'name' in body:
                vals['name'] = body['name']
            if 'code' in body:
                vals['code'] = body['code']
            if 'description' in body:
                vals['description'] = body['description']
            if vals:
                rec.write(vals)
            return _json_response({'data': _ser_role(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/roles/<int:rid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_role(self, rid, **kw):
        try:
            rec = request.env['encoach.role'].sudo().browse(rid)
            if rec.exists():
                rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/roles/<int:rid>/permissions', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_role_permissions(self, rid, **kw):
        try:
            rec = request.env['encoach.role'].sudo().browse(rid)
            if not rec.exists():
                return _error_response('Not found', 404)
            body = _get_json_body()
            perm_ids = body.get('permission_ids', [])
            rec.write({'permission_ids': [(6, 0, [int(p) for p in perm_ids])]})
            return _json_response({'data': _ser_role(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    # ── Permissions ──────────────────────────────────────────────────

    @http.route('/api/permissions', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_permissions(self, **kw):
        try:
            M = request.env['encoach.permission'].sudo()
            domain = []
            if kw.get('category'):
                domain.append(('topic', '=', kw['category']))
            if kw.get('search'):
                domain.append(('code', 'ilike', kw['search']))
            recs = M.search(domain, limit=500, order='topic, code')
            items = [_ser_perm(r) for r in recs]
            return _json_response({'data': items, 'total': len(items)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/permissions', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def create_permission(self, **kw):
        try:
            body = _get_json_body()
            code = body.get('code', body.get('name', '')).strip()
            if not code:
                return _error_response('Code is required', 400)
            vals = {'code': code}
            if body.get('name'):
                vals['name'] = body['name']
            if body.get('description'):
                vals['description'] = body['description']
            if body.get('topic') or body.get('category'):
                vals['topic'] = body.get('topic') or body.get('category', '')
            rec = request.env['encoach.permission'].sudo().create(vals)
            return _json_response({'data': _ser_perm(rec)})
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/permissions/<int:pid>', type='http', auth='public', methods=['DELETE'], csrf=False)
    @jwt_required
    def delete_permission(self, pid, **kw):
        try:
            rec = request.env['encoach.permission'].sudo().browse(pid)
            if rec.exists():
                rec.unlink()
            return _json_response({'success': True})
        except Exception as e:
            return _error_response(str(e), 500)

    # ── Authority Matrix ─────────────────────────────────────────────

    @http.route('/api/authority-matrix', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_authority_matrix(self, **kw):
        try:
            roles = request.env['encoach.role'].sudo().search([])
            perms = request.env['encoach.permission'].sudo().search([], order='topic, code')

            categories = {}
            for p in perms:
                cat = p.topic or 'General'
                categories.setdefault(cat, []).append(_ser_perm(p))

            role_list = []
            for r in roles:
                role_list.append({
                    'id': r.id,
                    'name': r.name or '',
                    'description': getattr(r, 'description', '') or '',
                    'granted_permission_ids': r.permission_ids.ids,
                })

            return _json_response({
                'roles': role_list,
                'permissions': [_ser_perm(p) for p in perms],
                'categories': categories,
                'matrix': {str(r.id): r.permission_ids.ids for r in roles},
                'total_roles': len(roles),
                'total_permissions': len(perms),
            })
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/authority-matrix/toggle', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def toggle_matrix(self, **kw):
        try:
            body = _get_json_body()
            role = request.env['encoach.role'].sudo().browse(int(body.get('role_id', 0)))
            perm_id = int(body.get('permission_id', 0))
            if not role.exists():
                return _error_response('Role not found', 404)
            current = role.permission_ids.ids
            if perm_id in current:
                role.write({'permission_ids': [(3, perm_id)]})
                granted = False
            else:
                role.write({'permission_ids': [(4, perm_id)]})
                granted = True
            return _json_response({'role_id': role.id, 'permission_id': perm_id, 'granted': granted})
        except Exception as e:
            return _error_response(str(e), 500)

    # ── User-Role Assignment ─────────────────────────────────────────

    @http.route('/api/users/with-roles', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def list_users_with_roles(self, **kw):
        try:
            M = request.env['res.users'].sudo()
            domain = [('share', '=', False)]
            if kw.get('search'):
                domain.append(('name', 'ilike', kw['search']))
            if kw.get('role_id'):
                domain.append(('role_ids', 'in', [int(kw['role_id'])]))
            offset, limit, page = _paginate(kw)
            total = M.search_count(domain)
            recs = M.search(domain, offset=offset, limit=limit, order='id desc')
            items = []
            for u in recs:
                role_ids = u.role_ids.ids if hasattr(u, 'role_ids') else []
                all_perms = u.get_all_permissions() if hasattr(u, 'get_all_permissions') else u.role_ids.mapped('permission_ids')
                items.append({
                    'id': u.id,
                    'name': u.name or '',
                    'login': u.login or '',
                    'email': u.email or u.login or '',
                    'active': u.active,
                    'role_ids': role_ids,
                    'roles': [{'id': r.id, 'name': r.name} for r in u.role_ids],
                    'effective_permission_count': len(all_perms),
                })
            return _json_response({'data': items, 'total': total, 'page': page, 'size': limit})
        except Exception as e:
            _logger.exception('list_users_with_roles')
            return _error_response(str(e), 500)

    @http.route('/api/users/<int:uid_param>/roles', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_user_roles(self, uid_param, **kw):
        try:
            user = request.env['res.users'].sudo().browse(uid_param)
            if not user.exists():
                return _error_response('User not found', 404)

            all_roles = request.env['encoach.role'].sudo().search([])
            assigned_ids = user.role_ids.ids if hasattr(user, 'role_ids') else []

            all_perms = user.get_all_permissions() if hasattr(user, 'get_all_permissions') else request.env['encoach.permission']

            roles_detail = []
            for r in all_roles:
                roles_detail.append({
                    'id': r.id,
                    'name': r.name or '',
                    'description': getattr(r, 'description', '') or '',
                    'assigned': r.id in assigned_ids,
                    'permission_count': len(r.permission_ids),
                })

            return _json_response({
                'user': {
                    'id': user.id,
                    'name': user.name or '',
                    'login': user.login or '',
                    'email': user.email or '',
                },
                'roles': roles_detail,
                'effective_permissions': [_ser_perm(p) for p in all_perms],
            })
        except Exception as e:
            _logger.exception('get_user_roles')
            return _error_response(str(e), 500)

    @http.route('/api/users/<int:uid_param>/roles', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False)
    @jwt_required
    def update_user_roles(self, uid_param, **kw):
        try:
            user = request.env['res.users'].sudo().browse(uid_param)
            if not user.exists():
                return _error_response('User not found', 404)
            body = _get_json_body()
            role_ids = body.get('role_ids', [])
            user.write({'role_ids': [(6, 0, [int(r) for r in role_ids])]})
            return _json_response({
                'success': True,
                'user_id': user.id,
                'role_ids': user.role_ids.ids,
            })
        except Exception as e:
            _logger.exception('update_user_roles')
            return _error_response(str(e), 500)

    @http.route('/api/users/<int:uid_param>/roles/toggle', type='http', auth='public', methods=['POST'], csrf=False)
    @jwt_required
    def toggle_user_role(self, uid_param, **kw):
        try:
            user = request.env['res.users'].sudo().browse(uid_param)
            if not user.exists():
                return _error_response('User not found', 404)
            body = _get_json_body()
            role_id = int(body.get('role_id', 0))
            if not role_id:
                return _error_response('Missing role_id', 400)

            current = user.role_ids.ids if hasattr(user, 'role_ids') else []
            if role_id in current:
                user.write({'role_ids': [(3, role_id)]})
                assigned = False
            else:
                user.write({'role_ids': [(4, role_id)]})
                assigned = True
            return _json_response({
                'user_id': user.id,
                'role_id': role_id,
                'assigned': assigned,
            })
        except Exception as e:
            return _error_response(str(e), 500)

    @http.route('/api/user-authority-matrix', type='http', auth='public', methods=['GET'], csrf=False)
    @jwt_required
    def get_user_authority_matrix(self, **kw):
        try:
            users = request.env['res.users'].sudo().search([
                ('share', '=', False),
            ], order='name', limit=200)
            perms = request.env['encoach.permission'].sudo().search([], order='topic, code')

            categories = {}
            for p in perms:
                cat = p.topic or 'General'
                categories.setdefault(cat, []).append(_ser_perm(p))

            user_list = []
            for u in users:
                all_perms = u.get_all_permissions() if hasattr(u, 'get_all_permissions') else u.role_ids.mapped('permission_ids')
                user_list.append({
                    'id': u.id,
                    'name': u.name or '',
                    'login': u.login or '',
                    'roles': [{'id': r.id, 'name': r.name} for r in u.role_ids],
                    'granted_permission_ids': all_perms.ids,
                })

            return _json_response({
                'users': user_list,
                'permissions': [_ser_perm(p) for p in perms],
                'categories': categories,
                'total_users': len(user_list),
                'total_permissions': len(perms),
            })
        except Exception as e:
            _logger.exception('get_user_authority_matrix')
            return _error_response(str(e), 500)
