import json
import calendar
from django.contrib.auth.models import User
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.views.decorators.http import require_POST
from django.utils import timezone
from django.db.models import Q, Count
from django.contrib import messages
from django.core.paginator import Paginator
from django.core.exceptions import ValidationError
from django.http import JsonResponse, FileResponse, Http404, HttpResponseForbidden
from client_portal.models import ClientProfile
from .models import (
    Role, StaffProfile, Client, Project, Task, TaskComment,
    DocumentRequest, AssetRequest, CompanyDocument,
    ActivityLog, ApprovalRequest, SystemNotification
)
from .forms import (
    ClientForm, ProjectForm, TaskForm, TaskCommentForm,
    DocumentRequestForm, DocumentRequestEditForm,
    AssetRequestForm, AssetRequestEditForm,
    CompanyDocumentForm, ClientPortalAccountForm,
    RoleEditorForm, UserRoleAssignForm,
)
from .decorators import staff_required, permission_required
from system.workflow_services import (
    on_task_status_changed,
    on_document_published,
    on_asset_published,
    on_activity_logged,
    create_approval_request,
)
from system.access_policy import get_default_dashboard_url, is_employee


def _safe_staff_profile(user):
    if not getattr(user, 'is_authenticated', False):
        return None
    try:
        return user.staff_profile
    except StaffProfile.DoesNotExist:
        return None


def _can_view_all_ops(user):
    profile = _safe_staff_profile(user)
    if getattr(user, 'is_superuser', False):
        return True
    return bool(profile and profile.has_perm('view_all_projects'))


def _can_access_project(user, project):
    if _can_view_all_ops(user):
        return True
    if project.project_manager_id == user.id:
        return True
    if project.team_members.filter(id=user.id).exists():
        return True
    return project.tasks.filter(assigned_to=user, is_active=True).exists()


def _can_access_task(user, task):
    if _can_view_all_ops(user):
        return True
    if task.project.project_manager_id == user.id:
        return True
    if task.project.team_members.filter(id=user.id).exists():
        return True
    return task.assigned_to.filter(id=user.id).exists()


def _paginate_queryset(request, queryset, *, per_page=25, page_param='page'):
    paginator = Paginator(queryset, per_page)
    return paginator.get_page(request.GET.get(page_param))


def _log_and_notify(actor, action, target, project=None, details='', link=''):
    """Helper: log activity AND fan-out notifications to all staff."""
    ActivityLog.log(actor, action, target, project=project, details=details)
    on_activity_logged(
        actor=actor,
        action=action,
        target_title=str(target),
        target_type=target.__class__.__name__,
        project=project,
        details=details,
        link=link,
    )


# ═══════════════════════════════════════
# AUTH
# ═══════════════════════════════════════

def staff_login(request):
    if request.user.is_authenticated:
        return redirect(get_default_dashboard_url(request.user))

    if request.method == 'POST':
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            user = form.get_user()
            login(request, user)
            route = get_default_dashboard_url(user)
            if route == 'client_portal:login':
                logout(request)
                messages.error(request, 'You do not have module access assigned.')
            else:
                return redirect(route)
        else:
            messages.error(request, 'Invalid credentials.')
    else:
        form = AuthenticationForm()

    return render(request, 'tasks/login.html', {'form': form})


def staff_logout(request):
    logout(request)
    return redirect('task_management:login')


# ═══════════════════════════════════════
# DASHBOARD
# ═══════════════════════════════════════

@staff_required
@permission_required('view_dashboard')
def dashboard(request):
    user = request.user
    profile = _safe_staff_profile(user)
    today = timezone.now().date()

    if _can_view_all_ops(user):
        active_projects = Project.objects.filter(is_active=True, status='active')
        all_tasks = Task.objects.filter(is_active=True)
    else:
        active_projects = Project.objects.filter(
            Q(project_manager=user) | Q(team_members=user),
            is_active=True, status='active'
        ).distinct()
        all_tasks = Task.objects.filter(assigned_to=user, is_active=True)

    overdue_tasks = all_tasks.filter(
        due_date__lt=today
    ).exclude(status='completed')

    my_tasks = Task.objects.filter(
        assigned_to=user, is_active=True
    ).exclude(status='completed').select_related('project')

    upcoming_deadlines = Task.objects.filter(
        assigned_to=user, is_active=True,
        due_date__gte=today,
        due_date__lte=today + timezone.timedelta(days=7)
    ).exclude(status='completed').select_related('project').order_by('due_date')[:5]

    projects_overview = active_projects.select_related('client')[:6]
    recent_activity = ActivityLog.objects.select_related('user', 'project')[:15]

    context = {
        'active_page': 'dashboard',
        'active_projects_count': active_projects.count(),
        'overdue_tasks_count': overdue_tasks.count(),
        'my_tasks_count': my_tasks.count(),
        'my_tasks': my_tasks[:5],
        'upcoming_deadlines': upcoming_deadlines,
        'projects_overview': projects_overview,
        'recent_activity': recent_activity,
        'overdue_tasks': overdue_tasks[:5],
    }
    return render(request, 'tasks/dashboard.html', context)


# ═══════════════════════════════════════
# MY PROFILE
# ═══════════════════════════════════════

@staff_required
def my_profile(request):
    user = request.user
    profile = _safe_staff_profile(user)
    assigned_tasks = Task.objects.filter(
        assigned_to=user, is_active=True
    ).exclude(status='completed').select_related('project')[:10]
    recent_activity = ActivityLog.objects.filter(user=user).select_related('project')[:10]
    context = {
        'active_page': 'me',
        'user_obj': user,
        'profile': profile,
        'assigned_tasks': assigned_tasks,
        'recent_activity': recent_activity,
    }
    return render(request, 'tasks/profile/me.html', context)


# ═══════════════════════════════════════
# USER MANAGEMENT (Admin — add new users)
# ═══════════════════════════════════════

@staff_required
@permission_required('manage_users')
def user_create(request):
    """Admin UI to create a new staff user directly."""
    if request.method == 'POST':
        username = request.POST.get('username', '').strip()
        email = request.POST.get('email', '').strip()
        first_name = request.POST.get('first_name', '').strip()
        last_name = request.POST.get('last_name', '').strip()
        password = request.POST.get('password', '').strip()
        password2 = request.POST.get('password2', '').strip()
        role_id = request.POST.get('role', '')
        is_superuser = request.POST.get('is_superuser') == 'on'

        errors = []
        if not username:
            errors.append('Username is required.')
        elif User.objects.filter(username=username).exists():
            errors.append('Username already taken.')
        if not email:
            errors.append('Email is required.')
        elif User.objects.filter(email=email).exists():
            errors.append('Email already in use.')
        if len(password) < 8:
            errors.append('Password must be at least 8 characters.')
        if password != password2:
            errors.append('Passwords do not match.')

        if errors:
            for e in errors:
                messages.error(request, e)
        else:
            user = User.objects.create_user(
                username=username,
                email=email,
                first_name=first_name,
                last_name=last_name,
                password=password,
                is_superuser=is_superuser,
                is_staff=is_superuser,
            )
            role = None
            if role_id:
                try:
                    role = Role.objects.get(id=role_id, is_active=True)
                except Role.DoesNotExist:
                    pass
            StaffProfile.objects.create(user=user, role=role, is_active=True)
            _log_and_notify(
                actor=request.user,
                action='created',
                target=user,
                details=f'Created new staff user: {username}',
                link=f'/staff/admin/users/{user.id}/access/',
            )
            messages.success(request, f'User "{username}" created successfully.')
            return redirect('task_management:staff_access_list')

    roles = Role.objects.filter(is_active=True).order_by('name')
    return render(request, 'tasks/users/create.html', {
        'active_page': 'admin_users',
        'roles': roles,
        'form_title': 'Add New Staff User',
    })


# ═══════════════════════════════════════
# CLIENTS
# ═══════════════════════════════════════

@staff_required
@permission_required('view_clients')
def client_list(request):
    query = request.GET.get('q', '')
    status_filter = request.GET.get('status', '')

    clients = Client.objects.filter(is_active=True)
    if query:
        clients = clients.filter(
            Q(name__icontains=query) | Q(company_name__icontains=query) | Q(email__icontains=query)
        )
    if status_filter:
        clients = clients.filter(status=status_filter)

    clients = clients.annotate(project_count=Count('projects', filter=Q(projects__is_active=True)))
    client_page = _paginate_queryset(request, clients, per_page=25)

    context = {
        'active_page': 'clients',
        'clients': client_page.object_list,
        'client_page': client_page,
        'query': query,
        'status_filter': status_filter,
    }
    return render(request, 'tasks/clients/list.html', context)


@staff_required
@permission_required('view_clients')
def client_detail(request, client_id):
    client = get_object_or_404(Client, id=client_id, is_active=True)
    projects = client.projects.filter(is_active=True).select_related('project_manager')
    context = {
        'active_page': 'clients',
        'client': client,
        'projects': projects,
    }
    return render(request, 'tasks/clients/detail.html', context)


@staff_required
@permission_required('create_client')
def client_create(request):
    if request.method == 'POST':
        form = ClientForm(request.POST)
        if form.is_valid():
            client = form.save(commit=False)
            client.created_by = request.user
            client.save()
            _log_and_notify(request.user, 'created', client,
                            link=f'/staff/clients/{client.id}/')
            messages.success(request, f'Client "{client.name}" created.')
            return redirect('task_management:client_detail', client_id=client.id)
    else:
        form = ClientForm()

    return render(request, 'tasks/clients/form.html', {
        'active_page': 'clients',
        'form': form,
        'form_title': 'New Client',
    })


@staff_required
@permission_required('edit_client')
def client_edit(request, client_id):
    client = get_object_or_404(Client, id=client_id, is_active=True)
    if request.method == 'POST':
        form = ClientForm(request.POST, instance=client)
        if form.is_valid():
            form.save()
            _log_and_notify(request.user, 'updated', client,
                            link=f'/staff/clients/{client.id}/')
            messages.success(request, f'Client "{client.name}" updated.')
            return redirect('task_management:client_detail', client_id=client.id)
    else:
        form = ClientForm(instance=client)

    return render(request, 'tasks/clients/form.html', {
        'active_page': 'clients',
        'form': form,
        'form_title': f'Edit: {client.name}',
        'client': client,
    })


@staff_required
@permission_required('delete_client')
@require_POST
def client_delete(request, client_id):
    client = get_object_or_404(Client, id=client_id)
    client.is_active = False
    client.save()
    _log_and_notify(request.user, 'deleted', client)
    messages.success(request, f'Client "{client.name}" archived.')
    return redirect('task_management:client_list')


@staff_required
@permission_required('edit_client')
def client_create_portal_account(request, client_id):
    client = get_object_or_404(Client, id=client_id, is_active=True)

    if hasattr(client, 'portal_profile'):
        messages.error(request, f'Portal account already exists for {client.name}.')
        return redirect('task_management:client_detail', client_id=client.id)

    show_credentials = False
    created_email = None
    created_password = None

    if request.method == 'POST':
        form = ClientPortalAccountForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data['email']
            first_name = form.cleaned_data['first_name']
            last_name = form.cleaned_data['last_name']
            password = form.cleaned_data['password']

            user = User.objects.create_user(
                username=email, email=email,
                first_name=first_name, last_name=last_name,
                password=password,
            )
            ClientProfile.objects.create(user=user, client=client)
            _log_and_notify(request.user, 'created', client,
                            details=f'Created portal account: {email}',
                            link=f'/staff/clients/{client.id}/')

            if form.cleaned_data.get('send_credentials'):
                show_credentials = True
                created_email = email
                created_password = password
                messages.success(request, f'Portal account created for {client.name}.')
            else:
                messages.success(request, f'Portal account created for {client.name}.')
                return redirect('task_management:client_detail', client_id=client.id)
    else:
        name_parts = client.name.split(' ', 1)
        form = ClientPortalAccountForm(initial={
            'email': client.email,
            'first_name': name_parts[0] if name_parts else '',
            'last_name': name_parts[1] if len(name_parts) > 1 else '',
        })

    return render(request, 'tasks/clients/create_portal_account.html', {
        'active_page': 'clients',
        'form': form,
        'client': client,
        'show_credentials': show_credentials,
        'created_email': created_email,
        'created_password': created_password,
    })


@staff_required
@permission_required('edit_client')
@require_POST
def client_reset_portal_password(request, client_id):
    client = get_object_or_404(Client, id=client_id, is_active=True)
    if not hasattr(client, 'portal_profile'):
        messages.error(request, 'No portal account exists for this client.')
        return redirect('task_management:client_detail', client_id=client.id)

    new_password = request.POST.get('new_password', '').strip()
    if len(new_password) < 8:
        messages.error(request, 'Password must be at least 8 characters.')
        return redirect('task_management:client_detail', client_id=client.id)

    portal_user = client.portal_profile.user
    portal_user.set_password(new_password)
    portal_user.save()
    _log_and_notify(request.user, 'updated', client,
                    details=f'Reset portal password for {portal_user.email}',
                    link=f'/staff/clients/{client.id}/')
    messages.success(request, f'Portal password reset for {portal_user.email}.')
    return redirect('task_management:client_detail', client_id=client.id)


@staff_required
@permission_required('edit_client')
@require_POST
def client_toggle_portal_access(request, client_id):
    client = get_object_or_404(Client, id=client_id, is_active=True)
    if not hasattr(client, 'portal_profile'):
        messages.error(request, 'No portal account exists for this client.')
        return redirect('task_management:client_detail', client_id=client.id)

    portal_user = client.portal_profile.user
    portal_user.is_active = not portal_user.is_active
    portal_user.save()
    status = 'enabled' if portal_user.is_active else 'disabled'
    _log_and_notify(request.user, 'updated', client,
                    details=f'Portal access {status} for {portal_user.email}',
                    link=f'/staff/clients/{client.id}/')
    messages.success(request, f'Portal access {status} for {portal_user.email}.')
    return redirect('task_management:client_detail', client_id=client.id)


# ═══════════════════════════════════════
# PROJECTS
# ═══════════════════════════════════════

@staff_required
@permission_required('view_projects')
def project_list(request):
    query = request.GET.get('q', '')
    status_filter = request.GET.get('status', '')

    if _can_view_all_ops(request.user):
        projects = Project.objects.filter(is_active=True)
    else:
        # Users see projects where they are a team member, project manager,
        # or assigned to at least one task
        projects = Project.objects.filter(
            Q(project_manager=request.user) |
            Q(team_members=request.user) |
            Q(tasks__assigned_to=request.user, tasks__is_active=True),
            is_active=True
        ).distinct()

    if query:
        projects = projects.filter(Q(name__icontains=query) | Q(client__name__icontains=query))
    if status_filter:
        projects = projects.filter(status=status_filter)

    projects = projects.select_related('client', 'project_manager')
    project_page = _paginate_queryset(request, projects, per_page=20)

    return render(request, 'tasks/projects/list.html', {
        'active_page': 'projects',
        'projects': project_page.object_list,
        'project_page': project_page,
        'query': query,
        'status_filter': status_filter,
    })


@staff_required
@permission_required('view_projects')
def project_detail(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if not _can_access_project(request.user, project):
        return HttpResponseForbidden('<h2>403 — Project Access Restricted</h2>')

    tasks = project.tasks.filter(is_active=True).prefetch_related('assigned_to')
    doc_requests = project.document_requests.filter(is_active=True)
    asset_requests = project.asset_requests.filter(is_active=True)
    activity = project.activity_logs.select_related('user')[:10]
    tasks_page = _paginate_queryset(request, tasks, per_page=20, page_param='tasks_page')

    return render(request, 'tasks/projects/detail.html', {
        'active_page': 'projects',
        'project': project,
        'tasks': tasks_page.object_list,
        'tasks_page': tasks_page,
        'doc_requests': doc_requests,
        'asset_requests': asset_requests,
        'activity': activity,
    })


@staff_required
@permission_required('create_project')
def project_create(request):
    initial = {}
    client_id = request.GET.get('client')
    if client_id:
        initial['client'] = client_id

    if request.method == 'POST':
        form = ProjectForm(request.POST)
        if form.is_valid():
            project = form.save(commit=False)
            project.created_by = request.user
            project.save()
            form.save_m2m()
            _log_and_notify(request.user, 'created', project, project=project,
                            link=f'/staff/projects/{project.id}/')
            messages.success(request, f'Project "{project.name}" created.')
            return redirect('task_management:project_detail', project_id=project.id)
    else:
        form = ProjectForm(initial=initial)

    return render(request, 'tasks/projects/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': 'New Project',
    })


@staff_required
@permission_required('edit_project')
def project_edit(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)

    if request.method == 'POST':
        form = ProjectForm(request.POST, instance=project)
        if form.is_valid():
            form.save()
            _log_and_notify(request.user, 'updated', project, project=project,
                            link=f'/staff/projects/{project.id}/')
            messages.success(request, f'Project "{project.name}" updated.')
            return redirect('task_management:project_detail', project_id=project.id)
    else:
        form = ProjectForm(instance=project)

    return render(request, 'tasks/projects/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': f'Edit: {project.name}',
        'project': project,
    })


@staff_required
@permission_required('delete_project')
@require_POST
def project_delete(request, project_id):
    project = get_object_or_404(Project, id=project_id)
    project.is_active = False
    project.save()
    _log_and_notify(request.user, 'deleted', project, project=project)
    messages.success(request, f'Project "{project.name}" archived.')
    return redirect('task_management:project_list')


# ═══════════════════════════════════════
# KANBAN
# ═══════════════════════════════════════

@staff_required
@permission_required('view_projects')
def project_kanban(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if not _can_access_project(request.user, project):
        return HttpResponseForbidden('<h2>403 — Project Access Restricted</h2>')

    tasks_by_status = project.get_tasks_by_status()
    return render(request, 'tasks/projects/kanban.html', {
        'active_page': 'projects',
        'project': project,
        'tasks_by_status': tasks_by_status,
        'kanban_statuses': ['backlog', 'todo', 'in_progress', 'review', 'completed'],
    })


@staff_required
@require_POST
def kanban_move_task(request):
    try:
        data = json.loads(request.body)
        task_id = data.get('task_id')
        new_status = data.get('status')
        new_order = data.get('order', 0)

        task = get_object_or_404(Task, id=task_id, is_active=True)
        profile = _safe_staff_profile(request.user)
        if not (getattr(request.user, 'is_superuser', False) or (profile and profile.has_perm('edit_task'))):
            return JsonResponse({'error': 'Permission denied'}, status=403)

        old_status = task.status
        task.status = new_status
        task.order = new_order

        if new_status == 'completed':
            task.progress_percentage = 100
        elif old_status == 'completed' and new_status != 'completed':
            task.progress_percentage = min(task.progress_percentage, 90)

        task.save()
        if old_status != new_status:
            on_task_status_changed(task=task, old_status=old_status, actor=request.user)

        _log_and_notify(request.user, 'moved', task, project=task.project,
                        details=f'Moved from {old_status} to {new_status}',
                        link=f'/staff/tasks/{task.id}/')

        return JsonResponse({
            'success': True,
            'task_id': task.id,
            'new_status': new_status,
            'project_progress': task.project.progress_percentage,
        })
    except (json.JSONDecodeError, KeyError) as e:
        return JsonResponse({'error': str(e)}, status=400)


# ═══════════════════════════════════════
# GANTT
# ═══════════════════════════════════════

@staff_required
@permission_required('view_projects')
def project_gantt(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if not _can_access_project(request.user, project):
        return HttpResponseForbidden('<h2>403 — Project Access Restricted</h2>')

    tasks = project.tasks.filter(is_active=True).order_by('start_date', 'order')
    total_days = project.total_project_days
    today_pct = (project.current_day / total_days * 100) if total_days > 0 else 0

    months = []
    cursor = project.start_date.replace(day=1)
    while cursor <= project.end_date:
        days_in_month = calendar.monthrange(cursor.year, cursor.month)[1]
        month_start = max(cursor, project.start_date)
        month_end_date = cursor.replace(day=days_in_month)
        month_end = min(month_end_date, project.end_date)
        effective_days = (month_end - month_start).days + 1
        months.append({'name': cursor.strftime('%b'), 'year': cursor.year, 'days': effective_days})
        if cursor.month == 12:
            cursor = cursor.replace(year=cursor.year + 1, month=1)
        else:
            cursor = cursor.replace(month=cursor.month + 1)

    task_data = []
    for task in tasks:
        assignees = list(task.assigned_to.all())
        task_data.append({
            'id': task.id,
            'name': task.title,
            'team': ', '.join([u.get_full_name() or u.username for u in assignees]) or 'Unassigned',
            'status': task.gantt_status,
            'left_pct': task.left_percent(),
            'width_pct': task.width_percent(),
        })

    return render(request, 'tasks/projects/gantt.html', {
        'active_page': 'projects',
        'project': project,
        'tasks': task_data,
        'months': months,
        'today_pct': today_pct,
    })


# ═══════════════════════════════════════
# TASKS
# ═══════════════════════════════════════

@staff_required
@permission_required('create_task')
def task_create(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)

    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            task = form.save(commit=False)
            task.project = project
            task.created_by = request.user
            task.save()
            form.save_m2m()
            _log_and_notify(request.user, 'created', task, project=project,
                            link=f'/staff/tasks/{task.id}/')
            messages.success(request, f'Task "{task.title}" created.')
            next_url = request.GET.get('next', '')
            if next_url == 'kanban':
                return redirect('task_management:project_kanban', project_id=project.id)
            return redirect('task_management:project_detail', project_id=project.id)
    else:
        form = TaskForm()

    return render(request, 'tasks/task/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': 'New Task',
        'project': project,
    })


@staff_required
@permission_required('view_tasks')
def task_detail(request, task_id):
    task = get_object_or_404(
        Task.objects.select_related('project', 'project__client', 'created_by')
                    .prefetch_related('assigned_to', 'comments__user'),
        id=task_id, is_active=True
    )
    if not _can_access_task(request.user, task):
        return HttpResponseForbidden('<h2>403 — Task Access Restricted</h2>')

    comments = task.comments.select_related('user').order_by('created_at')
    comment_form = TaskCommentForm()

    if request.method == 'POST':
        profile = _safe_staff_profile(request.user)
        if getattr(request.user, 'is_superuser', False) or (profile and profile.has_perm('create_comment')):
            comment_form = TaskCommentForm(request.POST, request.FILES)
            if comment_form.is_valid():
                comment = comment_form.save(commit=False)
                comment.task = task
                comment.user = request.user
                comment.save()
                _log_and_notify(request.user, 'commented', task, project=task.project,
                                details=comment.comment[:100],
                                link=f'/staff/tasks/{task.id}/')

                if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                    if profile:
                        initials = profile.initials
                        color = profile.resolved_avatar_color
                    else:
                        un = (request.user.get_full_name() or request.user.username or '?').strip()
                        initials = (un[:2] or '?').upper()
                        color = '#6366f1'
                    return JsonResponse({
                        'success': True,
                        'comment': {
                            'id': comment.id,
                            'user': request.user.get_full_name(),
                            'initials': initials,
                            'color': color,
                            'comment': comment.comment,
                            'created_at': comment.created_at.strftime('%d %b %Y, %H:%M'),
                        }
                    })
                messages.success(request, 'Comment added.')
                return redirect('task_management:task_detail', task_id=task.id)

    return render(request, 'tasks/task/detail.html', {
        'active_page': 'projects',
        'task': task,
        'project': task.project,
        'comments': comments,
        'comment_form': comment_form,
    })


@staff_required
@permission_required('edit_task')
def task_edit(request, task_id):
    task = get_object_or_404(Task, id=task_id, is_active=True)
    if not _can_access_task(request.user, task):
        return HttpResponseForbidden('<h2>403 — Task Access Restricted</h2>')

    if request.method == 'POST':
        old_status = task.status
        form = TaskForm(request.POST, instance=task)
        if form.is_valid():
            form.save()
            _log_and_notify(request.user, 'updated', task, project=task.project,
                            link=f'/staff/tasks/{task.id}/')
            if old_status != task.status:
                on_task_status_changed(task=task, old_status=old_status, actor=request.user)
            messages.success(request, f'Task "{task.title}" updated.')
            return redirect('task_management:task_detail', task_id=task.id)
    else:
        form = TaskForm(instance=task)

    return render(request, 'tasks/task/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': f'Edit: {task.title}',
        'task': task,
        'project': task.project,
    })


@staff_required
@permission_required('delete_task')
@require_POST
def task_delete(request, task_id):
    task = get_object_or_404(Task, id=task_id)
    if not _can_access_task(request.user, task):
        return HttpResponseForbidden()
    project_id = task.project_id
    task.is_active = False
    task.save()
    _log_and_notify(request.user, 'deleted', task, project=task.project)
    messages.success(request, f'Task "{task.title}" archived.')
    return redirect('task_management:project_detail', project_id=project_id)


@staff_required
@require_POST
def task_update_progress(request, task_id):
    task = get_object_or_404(Task, id=task_id, is_active=True)
    if not _can_access_task(request.user, task):
        return JsonResponse({'error': 'Task access restricted'}, status=403)

    profile = _safe_staff_profile(request.user)
    if not (getattr(request.user, 'is_superuser', False) or (profile and profile.has_perm('edit_task'))):
        return JsonResponse({'error': 'Permission denied'}, status=403)

    try:
        data = json.loads(request.body)
        progress = max(0, min(100, int(data.get('progress', 0))))
        task.progress_percentage = progress
        if progress == 100:
            task.status = 'completed'
        task.save()
        _log_and_notify(request.user, 'updated', task, project=task.project,
                        details=f'Progress updated to {progress}%',
                        link=f'/staff/tasks/{task.id}/')
        return JsonResponse({
            'success': True,
            'progress': task.progress_percentage,
            'status': task.status,
            'project_progress': task.project.progress_percentage,
        })
    except (json.JSONDecodeError, ValueError) as e:
        return JsonResponse({'error': str(e)}, status=400)


# ═══════════════════════════════════════
# DOCUMENT REQUESTS
# ═══════════════════════════════════════

@staff_required
@permission_required('view_documents')
def document_request_list(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if not _can_access_project(request.user, project):
        return HttpResponseForbidden()
    doc_requests = project.document_requests.filter(is_active=True)
    docs_page = _paginate_queryset(request, doc_requests, per_page=20, page_param='docs_page')
    return render(request, 'tasks/documents/list.html', {
        'active_page': 'projects',
        'project': project,
        'doc_requests': docs_page.object_list,
        'docs_page': docs_page,
    })


@staff_required
@permission_required('request_documents')
def document_request_create(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if request.method == 'POST':
        form = DocumentRequestForm(request.POST, request.FILES)
        if form.is_valid():
            doc_req = form.save(commit=False)
            doc_req.project = project
            doc_req.created_by = request.user
            doc_req.save()
            _log_and_notify(request.user, 'created', doc_req, project=project,
                            link=f'/staff/projects/{project.id}/documents/')
            messages.success(request, f'Document request "{doc_req.title}" created.')
            return redirect('task_management:document_request_list', project_id=project.id)
    else:
        form = DocumentRequestForm()
    return render(request, 'tasks/documents/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': 'New Document Request',
        'project': project,
    })


@staff_required
@permission_required('request_documents')
@require_POST
def document_request_push(request, doc_req_id):
    doc_req = get_object_or_404(DocumentRequest, id=doc_req_id, is_active=True)
    doc_req.send_to_portal()
    on_document_published(document_request=doc_req, actor=request.user)
    _log_and_notify(request.user, 'updated', doc_req, project=doc_req.project,
                    details='Sent to client portal',
                    link=f'/staff/projects/{doc_req.project_id}/documents/')
    messages.success(request, f'"{doc_req.title}" is now visible on the client portal.')
    return redirect('task_management:document_request_list', project_id=doc_req.project_id)


@staff_required
@permission_required('request_documents')
def document_request_edit(request, doc_req_id):
    doc_req = get_object_or_404(DocumentRequest, id=doc_req_id, is_active=True)
    if doc_req.signed:
        messages.error(request, 'Signed documents cannot be edited.')
        return redirect('task_management:document_request_list', project_id=doc_req.project_id)

    if request.method == 'POST':
        form = DocumentRequestEditForm(request.POST, request.FILES, instance=doc_req)
        if form.is_valid():
            form.save()
            _log_and_notify(request.user, 'updated', doc_req, project=doc_req.project,
                            link=f'/staff/projects/{doc_req.project_id}/documents/')
            messages.success(request, f'Document request "{doc_req.title}" updated.')
            return redirect('task_management:document_request_list', project_id=doc_req.project_id)
    else:
        form = DocumentRequestEditForm(instance=doc_req)

    return render(request, 'tasks/documents/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': f'Edit: {doc_req.title}',
        'project': doc_req.project,
        'doc_req': doc_req,
        'is_edit': True,
    })


@staff_required
@permission_required('request_documents')
@require_POST
def document_request_delete(request, doc_req_id):
    doc_req = get_object_or_404(DocumentRequest, id=doc_req_id, is_active=True)
    project_id = doc_req.project_id
    if doc_req.signed:
        messages.error(request, 'Signed documents cannot be deleted.')
        return redirect('task_management:document_request_list', project_id=project_id)
    doc_req.is_active = False
    doc_req.save()
    _log_and_notify(request.user, 'deleted', doc_req, project=doc_req.project)
    messages.success(request, f'Document request "{doc_req.title}" archived.')
    return redirect('task_management:document_request_list', project_id=project_id)


@staff_required
def document_request_download(request, doc_req_id, file_type):
    doc_req = get_object_or_404(DocumentRequest, id=doc_req_id, is_active=True)
    if file_type == 'original' and doc_req.file:
        return FileResponse(doc_req.file.open('rb'), as_attachment=True,
                            filename=doc_req.file.name.split('/')[-1])
    elif file_type == 'signed' and doc_req.signed_file:
        return FileResponse(doc_req.signed_file.open('rb'), as_attachment=True,
                            filename=doc_req.signed_file.name.split('/')[-1])
    raise Http404("File not found")


@staff_required
def document_request_preview(request, doc_req_id):
    doc_req = get_object_or_404(DocumentRequest, id=doc_req_id, is_active=True)
    return JsonResponse({
        'id': doc_req.id,
        'title': doc_req.title,
        'description': doc_req.description,
        'preview_text': doc_req.preview_text,
        'requires_signature': doc_req.requires_signature,
        'signed': doc_req.signed,
        'signed_by': doc_req.signed_by,
        'signed_at': doc_req.signed_at.strftime('%d %b %Y, %H:%M') if doc_req.signed_at else None,
        'status': doc_req.get_status_display(),
        'deadline': doc_req.deadline.strftime('%d %b %Y') if doc_req.deadline else None,
        'has_file': bool(doc_req.file),
        'has_signed_file': bool(doc_req.signed_file),
        'portal_visible': doc_req.portal_visible,
    })


# ═══════════════════════════════════════
# ASSET REQUESTS
# ═══════════════════════════════════════

@staff_required
@permission_required('view_assets')
def asset_request_list(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if not _can_access_project(request.user, project):
        return HttpResponseForbidden()
    asset_requests = project.asset_requests.filter(is_active=True)
    assets_page = _paginate_queryset(request, asset_requests, per_page=20, page_param='assets_page')
    return render(request, 'tasks/assets/list.html', {
        'active_page': 'projects',
        'project': project,
        'asset_requests': assets_page.object_list,
        'assets_page': assets_page,
    })


@staff_required
@permission_required('request_assets')
def asset_request_create(request, project_id):
    project = get_object_or_404(Project, id=project_id, is_active=True)
    if request.method == 'POST':
        form = AssetRequestForm(request.POST)
        if form.is_valid():
            asset_req = form.save(commit=False)
            asset_req.project = project
            asset_req.created_by = request.user
            asset_req.save()
            _log_and_notify(request.user, 'created', asset_req, project=project,
                            link=f'/staff/projects/{project.id}/assets/')
            messages.success(request, f'Asset request "{asset_req.title}" created.')
            return redirect('task_management:asset_request_list', project_id=project.id)
    else:
        form = AssetRequestForm()
    return render(request, 'tasks/assets/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': 'New Asset Request',
        'project': project,
    })


@staff_required
@permission_required('request_assets')
@require_POST
def asset_request_push(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    asset_req.send_to_portal()
    on_asset_published(asset_request=asset_req, actor=request.user)
    _log_and_notify(request.user, 'updated', asset_req, project=asset_req.project,
                    details='Sent to client portal',
                    link=f'/staff/projects/{asset_req.project_id}/assets/')
    messages.success(request, f'"{asset_req.title}" is now visible on the client portal.')
    return redirect('task_management:asset_request_list', project_id=asset_req.project_id)


@staff_required
@permission_required('request_assets')
def asset_request_edit(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    if asset_req.status == 'approved':
        messages.error(request, 'Approved assets cannot be edited.')
        return redirect('task_management:asset_request_list', project_id=asset_req.project_id)

    if request.method == 'POST':
        form = AssetRequestEditForm(request.POST, instance=asset_req)
        if form.is_valid():
            form.save()
            _log_and_notify(request.user, 'updated', asset_req, project=asset_req.project,
                            link=f'/staff/projects/{asset_req.project_id}/assets/')
            messages.success(request, f'Asset request "{asset_req.title}" updated.')
            return redirect('task_management:asset_request_list', project_id=asset_req.project_id)
    else:
        form = AssetRequestEditForm(instance=asset_req)
    return render(request, 'tasks/assets/form.html', {
        'active_page': 'projects',
        'form': form,
        'form_title': f'Edit: {asset_req.title}',
        'project': asset_req.project,
        'is_edit': True,
    })


@staff_required
@permission_required('request_assets')
@require_POST
def asset_request_delete(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    project_id = asset_req.project_id
    if asset_req.status == 'approved':
        messages.error(request, 'Approved assets cannot be deleted.')
        return redirect('task_management:asset_request_list', project_id=project_id)
    asset_req.is_active = False
    asset_req.save()
    _log_and_notify(request.user, 'deleted', asset_req, project=asset_req.project)
    messages.success(request, f'Asset request "{asset_req.title}" archived.')
    return redirect('task_management:asset_request_list', project_id=project_id)


@staff_required
def asset_request_download(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    if not asset_req.file:
        raise Http404("No file uploaded")
    return FileResponse(asset_req.file.open('rb'), as_attachment=True,
                        filename=asset_req.file_name or asset_req.file.name.split('/')[-1])


@staff_required
def asset_request_preview(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    file_name = asset_req.file_name or ''
    file_ext = file_name.rsplit('.', 1)[-1].lower() if '.' in file_name else ''
    is_image = file_ext in ('jpg', 'jpeg', 'png', 'gif', 'webp', 'svg')
    return JsonResponse({
        'id': asset_req.id,
        'title': asset_req.title,
        'description': asset_req.description,
        'required': asset_req.required,
        'status': asset_req.get_status_display(),
        'deadline': asset_req.deadline.strftime('%d %b %Y') if asset_req.deadline else None,
        'has_file': bool(asset_req.file),
        'file_name': file_name,
        'file_url': asset_req.file.url if asset_req.file else None,
        'is_image': is_image,
        'uploaded_at': asset_req.uploaded_at.strftime('%d %b %Y, %H:%M') if asset_req.uploaded_at else None,
        'portal_visible': asset_req.portal_visible,
    })


@staff_required
@permission_required('approve_asset_requests')
@require_POST
def asset_request_approve(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    if asset_req.status != 'uploaded':
        messages.error(request, 'Only uploaded assets can be approved.')
        return redirect('task_management:asset_request_list', project_id=asset_req.project_id)
    asset_req.status = 'approved'
    asset_req.save()
    _log_and_notify(request.user, 'updated', asset_req, project=asset_req.project,
                    details='Approved asset',
                    link=f'/staff/projects/{asset_req.project_id}/assets/')
    messages.success(request, f'Asset "{asset_req.title}" approved.')
    return redirect('task_management:asset_request_list', project_id=asset_req.project_id)


@staff_required
@permission_required('approve_asset_requests')
@require_POST
def asset_request_reject(request, asset_req_id):
    asset_req = get_object_or_404(AssetRequest, id=asset_req_id, is_active=True)
    asset_req.status = 'requested'
    asset_req.file = None
    asset_req.file_name = ''
    asset_req.uploaded_at = None
    asset_req.save()
    _log_and_notify(request.user, 'updated', asset_req, project=asset_req.project,
                    details='Rejected asset, requesting re-upload',
                    link=f'/staff/projects/{asset_req.project_id}/assets/')
    messages.success(request, f'Asset "{asset_req.title}" rejected. Client will need to re-upload.')
    return redirect('task_management:asset_request_list', project_id=asset_req.project_id)


# ═══════════════════════════════════════
# COMPANY DOCUMENTS
# ═══════════════════════════════════════

@staff_required
@permission_required('view_company_documents')
def company_document_list(request):
    query = request.GET.get('q', '').strip()
    docs = CompanyDocument.objects.filter(is_active=True).select_related('uploaded_by')

    if query:
        docs = docs.filter(
            Q(title__icontains=query) | Q(description__icontains=query) |
            Q(uploaded_by__first_name__icontains=query) | Q(uploaded_by__last_name__icontains=query)
        )

    if request.method == 'POST':
        sp = _safe_staff_profile(request.user)
        if not (getattr(request.user, 'is_superuser', False) or (sp and sp.has_perm('upload_company_documents'))):
            return HttpResponseForbidden()
        form = CompanyDocumentForm(request.POST, request.FILES)
        if form.is_valid():
            doc = form.save(commit=False)
            doc.uploaded_by = request.user
            doc.save()
            _log_and_notify(request.user, 'uploaded', doc,
                            details=f'Uploaded: {doc.title}',
                            link='/staff/company-documents/')
            messages.success(request, f'Document "{doc.title}" uploaded.')
            return redirect('task_management:company_document_list')
    else:
        form = CompanyDocumentForm()

    docs_page = _paginate_queryset(request, docs, per_page=20, page_param='docs_page')
    return render(request, 'tasks/company_docs/list.html', {
        'active_page': 'company_docs',
        'form': form,
        'documents': docs_page.object_list,
        'docs_page': docs_page,
        'query': query,
    })


@staff_required
@permission_required('view_company_documents')
def company_document_download(request, doc_id):
    doc = get_object_or_404(CompanyDocument, id=doc_id, is_active=True)
    if not doc.file:
        raise Http404("No file found")
    return FileResponse(doc.file.open('rb'), as_attachment=True, filename=doc.filename)


@staff_required
@require_POST
def company_document_delete(request, doc_id):
    doc = get_object_or_404(CompanyDocument, id=doc_id, is_active=True)
    profile = _safe_staff_profile(request.user)
    is_owner = doc.uploaded_by_id == request.user.id
    can_delete = getattr(request.user, 'is_superuser', False) or (profile and profile.has_perm('delete_company_documents'))
    if not (is_owner or can_delete):
        return HttpResponseForbidden()
    doc.is_active = False
    doc.save(update_fields=['is_active', 'updated_at'])
    _log_and_notify(request.user, 'deleted', doc, details=f'Archived: {doc.title}')
    messages.success(request, f'Document "{doc.title}" archived.')
    return redirect('task_management:company_document_list')


# ═══════════════════════════════════════
# NOTIFICATIONS & APPROVALS
# ═══════════════════════════════════════

@staff_required
def notification_list(request):
    notifications = request.user.system_notifications.order_by('-created_at')[:100]
    unread = request.user.system_notifications.filter(is_read=False).count()
    return render(request, 'tasks/notifications/list.html', {
        'active_page': 'dashboard',
        'notifications': notifications,
        'unread_count': unread,
    })


@staff_required
def notification_detail(request, notification_id):
    note = get_object_or_404(SystemNotification, id=notification_id, recipient=request.user)
    if not note.is_read:
        note.mark_read()
    return render(request, 'tasks/notifications/detail.html', {
        'active_page': 'dashboard',
        'note': note,
    })


@staff_required
@require_POST
def notification_mark_read(request, notification_id):
    note = get_object_or_404(SystemNotification, id=notification_id, recipient=request.user)
    note.mark_read()
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return JsonResponse({'success': True, 'unread': request.user.system_notifications.filter(is_read=False).count()})
    return redirect(request.META.get('HTTP_REFERER', 'task_management:notification_list'))


@staff_required
@permission_required('view_approvals')
def approval_queue(request):
    approvals = ApprovalRequest.objects.select_related('requested_by', 'approved_by').order_by('-created_at')
    return render(request, 'tasks/approvals/list.html', {
        'active_page': 'dashboard',
        'approvals': approvals[:200],
        'pending_count': approvals.filter(status='pending').count(),
    })


@staff_required
@require_POST
@permission_required('manage_approvals')
def approval_action(request, approval_id):
    approval = get_object_or_404(ApprovalRequest, id=approval_id)
    action = request.POST.get('action')
    if action == 'approve':
        approval.approve(request.user)
    elif action == 'reject':
        approval.reject(request.user)
    return redirect('task_management:approval_queue')


# ═══════════════════════════════════════
# ROLES & TEAM ACCESS
# ═══════════════════════════════════════

@staff_required
@permission_required('manage_roles')
def role_list(request):
    roles = Role.objects.filter(is_active=True).order_by('name')
    return render(request, 'tasks/roles/list.html', {
        'active_page': 'roles',
        'roles': roles,
    })


@staff_required
@permission_required('manage_roles')
def role_create(request):
    if request.method == 'POST':
        form = RoleEditorForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Role created.')
            return redirect('task_management:role_list')
    else:
        form = RoleEditorForm()
    return render(request, 'tasks/roles/form.html', {
        'active_page': 'roles',
        'form': form,
        'form_title': 'New Role',
        'catalog': Role.get_effective_permission_catalog(),
    })


@staff_required
@permission_required('manage_roles')
def role_edit(request, role_id):
    role = get_object_or_404(Role, id=role_id, is_active=True)
    if request.method == 'POST':
        form = RoleEditorForm(request.POST, instance=role)
        if form.is_valid():
            form.save()
            messages.success(request, 'Role updated.')
            return redirect('task_management:role_list')
    else:
        form = RoleEditorForm(instance=role)
    return render(request, 'tasks/roles/form.html', {
        'active_page': 'roles',
        'form': form,
        'form_title': f'Edit: {role.name}',
        'role': role,
        'catalog': Role.get_effective_permission_catalog(),
    })


@staff_required
@permission_required('manage_roles')
@require_POST
def role_delete(request, role_id):
    role = get_object_or_404(Role, id=role_id)
    role.is_active = False
    role.save(update_fields=['is_active'])
    messages.success(request, 'Role archived.')
    return redirect('task_management:role_list')


@staff_required
@permission_required('manage_roles')
def staff_access_list(request):
    from django.db.models import Exists, OuterRef
    qs = User.objects.filter(is_active=True).annotate(
        _is_client=Exists(ClientProfile.objects.filter(user_id=OuterRef('pk'))),
    ).order_by('first_name', 'last_name')

    access_rows = []
    for u in qs:
        sp = _safe_staff_profile(u)
        access_rows.append({'user': u, 'profile': sp, 'is_client': u._is_client})

    return render(request, 'tasks/roles/staff_access.html', {
        'active_page': 'admin_users',
        'access_rows': access_rows,
    })


@staff_required
@permission_required('manage_roles')
def user_assign_role(request, user_id):
    from django.core.cache import cache
    target = get_object_or_404(User, id=user_id, is_active=True)

    if request.method == 'POST':
        form = UserRoleAssignForm(request.POST)
        if form.is_valid():
            role = form.cleaned_data['role']
            active = form.cleaned_data['staff_access_active'] == 'yes'
            profile = _safe_staff_profile(target)
            if profile is None:
                if role is not None or active:
                    StaffProfile.objects.create(user=target, role=role, is_active=active)
                    messages.success(request, 'Staff access configured.')
            else:
                profile.role = role
                profile.is_active = active
                profile.save(update_fields=['role', 'is_active'])
                messages.success(request, 'Role and access updated.')
            cache.delete('task_management:active_user_ids:v1')
            return redirect('task_management:staff_access_list')
    else:
        sp = _safe_staff_profile(target)
        initial = {'staff_access_active': 'yes'}
        if sp:
            initial['role'] = sp.role_id
            initial['staff_access_active'] = 'yes' if sp.is_active else 'no'
        form = UserRoleAssignForm(initial=initial)

    is_client = ClientProfile.objects.filter(user=target).exists()
    return render(request, 'tasks/roles/user_assign.html', {
        'active_page': 'admin_users',
        'target_user': target,
        'form': form,
        'is_client': is_client,
    })


# ═══════════════════════════════════════
# INTERNAL API
# ═══════════════════════════════════════

@staff_required
def api_clients(request):
    data = list(Client.objects.filter(is_active=True).values(
        'id', 'name', 'company_name', 'email', 'phone', 'status'
    ))
    return JsonResponse({'results': data})


@staff_required
def api_projects(request):
    data = list(Project.objects.filter(is_active=True).values(
        'id', 'name', 'client_id', 'status', 'priority', 'start_date', 'end_date'
    )[:300])
    return JsonResponse({'results': data})


@staff_required
def api_ops_summary(request):
    return JsonResponse({
        'active_clients': Client.objects.filter(is_active=True).count(),
        'active_projects': Project.objects.filter(is_active=True, status='active').count(),
        'open_tasks': Task.objects.filter(is_active=True).exclude(status='completed').count(),
        'pending_approvals': ApprovalRequest.objects.filter(status='pending').count(),
    })