# task_management/models.py
from django.db import models
from django.db.models import Q
from django.conf import settings
from django.utils import timezone
from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError
from decimal import Decimal


# ═══════════════════════════════════════
# ROLE SYSTEM
# ═══════════════════════════════════════

class Role(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(blank=True)
    permissions = models.JSONField(default=dict)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

    def has_permission(self, perm_key):
        return self.permissions.get(perm_key, False)

    @classmethod
    def get_default_permissions(cls):
        return {
            # Dashboard
            'view_dashboard': True,
            # Clients
            'view_clients': True,
            'create_client': False,
            'edit_client': False,
            'delete_client': False,
            # Projects
            'view_projects': True,
            'create_project': False,
            'edit_project': False,
            'delete_project': False,
            'view_all_projects': False,
            # Tasks
            'view_tasks': True,
            'create_task': False,
            'edit_task': False,
            'delete_task': False,
            'assign_task': False,
            'create_comment': False,
            'delete_comment': False,
            # Documents
            'view_documents': True,
            'request_documents': False,
            'edit_document_requests': False,
            'delete_document_requests': False,
            'publish_documents': False,
            # Assets
            'view_assets': True,
            'request_assets': False,
            'edit_asset_requests': False,
            'delete_asset_requests': False,
            'approve_asset_requests': False,
            'publish_assets': False,
            # Approvals
            'view_approvals': False,
            'manage_approvals': False,
            # Company Docs
            'view_company_documents': True,
            'upload_company_documents': False,
            'delete_company_documents': False,
            # Administration
            'manage_roles': False,
            'manage_users': False,
        }

    @classmethod
    def get_permission_catalog(cls):
        return {
            'Core': [
                ('view_dashboard', 'View Dashboard'),
            ],
            'Clients': [
                ('view_clients', 'View Clients'),
                ('create_client', 'Create Clients'),
                ('edit_client', 'Edit Clients'),
                ('delete_client', 'Delete Clients'),
            ],
            'Projects': [
                ('view_projects', 'View Projects'),
                ('create_project', 'Create Projects'),
                ('edit_project', 'Edit Projects'),
                ('delete_project', 'Delete Projects'),
                ('view_all_projects', 'View All Projects'),
            ],
            'Tasks': [
                ('view_tasks', 'View Tasks'),
                ('create_task', 'Create Tasks'),
                ('edit_task', 'Edit Tasks'),
                ('delete_task', 'Delete Tasks'),
                ('assign_task', 'Assign Tasks'),
                ('create_comment', 'Create Task Comments'),
                ('delete_comment', 'Delete Task Comments'),
            ],
            'Documents': [
                ('view_documents', 'View Document Requests'),
                ('request_documents', 'Create Document Requests'),
                ('edit_document_requests', 'Edit Document Requests'),
                ('delete_document_requests', 'Delete Document Requests'),
                ('publish_documents', 'Publish Documents to Portal'),
            ],
            'Assets': [
                ('view_assets', 'View Asset Requests'),
                ('request_assets', 'Create Asset Requests'),
                ('edit_asset_requests', 'Edit Asset Requests'),
                ('delete_asset_requests', 'Delete Asset Requests'),
                ('approve_asset_requests', 'Approve / Reject Assets'),
                ('publish_assets', 'Publish Assets to Portal'),
            ],
            'Approvals': [
                ('view_approvals', 'View Approvals Queue'),
                ('manage_approvals', 'Take Approval Actions'),
            ],
            'Company Docs': [
                ('view_company_documents', 'View Company Documents'),
                ('upload_company_documents', 'Upload Company Documents'),
                ('delete_company_documents', 'Delete Company Documents'),
            ],
            'Administration': [
                ('manage_roles', 'Manage Roles & Permissions'),
                ('manage_users', 'Manage Users'),
            ],
        }

    @classmethod
    def get_effective_permission_catalog(cls):
        return cls.get_permission_catalog()


class StaffProfile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='staff_profile'
    )
    role = models.ForeignKey(
        Role, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='staff_members'
    )
    job_title = models.CharField(max_length=200, blank=True)
    avatar_color = models.CharField(max_length=7, default='#3b82f6')
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['user__first_name', 'user__last_name']

    def __str__(self):
        return f"{self.user.get_full_name()} ({self.job_title or 'Staff'})"

    @property
    def initials(self):
        first = self.user.first_name[:1] if self.user.first_name else ''
        last = self.user.last_name[:1] if self.user.last_name else ''
        return (first + last).upper() or self.user.username[:2].upper()

    @property
    def resolved_avatar_color(self):
        return self.avatar_color

    @property
    def resolved_job_title(self):
        return self.job_title or 'Staff'

    def has_perm(self, perm_key):
        if not self.role:
            return False
        return self.role.has_permission(perm_key)

    @classmethod
    def active_user_queryset(cls):
        from django.contrib.auth import get_user_model
        User = get_user_model()
        cache_key = 'task_management:active_user_ids:v1'
        user_ids = cache.get(cache_key)
        if user_ids is None:
            user_ids = list(
                User.objects.filter(
                    staff_profile__is_active=True
                ).values_list('id', flat=True).distinct()
            )
            cache.set(cache_key, user_ids, 300)
        return User.objects.filter(id__in=user_ids).order_by('first_name', 'last_name')


# ═══════════════════════════════════════
# CLIENT
# ═══════════════════════════════════════

class Client(models.Model):
    STATUS_CHOICES = [
        ('active', 'Active'),
        ('inactive', 'Inactive'),
    ]

    name = models.CharField(max_length=200)
    company_name = models.CharField(max_length=300)
    email = models.EmailField()
    phone = models.CharField(max_length=50, blank=True)
    address = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
    notes = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='created_clients'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.name} — {self.company_name}"

    def clean(self):
        duplicates = Client.objects.filter(is_active=True)
        if self.pk:
            duplicates = duplicates.exclude(pk=self.pk)
        email = (self.email or '').strip()
        company_name = (self.company_name or '').strip()
        if email and duplicates.filter(email__iexact=email).exists():
            raise ValidationError({'email': 'An active client with this email already exists.'})
        if company_name and duplicates.filter(company_name__iexact=company_name).exists():
            raise ValidationError({'company_name': 'An active client with this company name already exists.'})

    @property
    def active_projects_count(self):
        return self.projects.filter(is_active=True, status='active').count()

    @property
    def total_projects_count(self):
        return self.projects.filter(is_active=True).count()


# ═══════════════════════════════════════
# PROJECT
# ═══════════════════════════════════════

class Project(models.Model):
    STATUS_CHOICES = [
        ('planning', 'Planning'),
        ('active', 'Active'),
        ('on_hold', 'On Hold'),
        ('completed', 'Completed'),
        ('cancelled', 'Cancelled'),
    ]

    PRIORITY_CHOICES = [
        ('low', 'Low'),
        ('medium', 'Medium'),
        ('high', 'High'),
        ('urgent', 'Urgent'),
    ]

    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='projects')
    name = models.CharField(max_length=300)
    description = models.TextField(blank=True)
    start_date = models.DateField()
    end_date = models.DateField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planning')
    priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
    project_manager = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='managed_projects'
    )
    team_members = models.ManyToManyField(
        settings.AUTH_USER_MODEL, blank=True, related_name='team_projects'
    )
    team_size = models.PositiveIntegerField(default=1)
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='created_projects'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return self.name

    @property
    def progress_percentage(self):
        tasks = self.tasks.filter(is_active=True)
        if not tasks.exists():
            return 0
        total = sum(t.progress_percentage for t in tasks)
        return round(total / tasks.count())

    @property
    def completed_tasks_count(self):
        return self.tasks.filter(is_active=True, status='completed').count()

    @property
    def total_tasks_count(self):
        return self.tasks.filter(is_active=True).count()

    @property
    def overdue_tasks_count(self):
        return self.tasks.filter(
            is_active=True, due_date__lt=timezone.now().date()
        ).exclude(status='completed').count()

    @property
    def days_remaining(self):
        delta = self.end_date - timezone.now().date()
        return max(delta.days, 0)

    @property
    def is_overdue(self):
        return self.end_date < timezone.now().date() and self.status != 'completed'

    @property
    def total_project_days(self):
        return max((self.end_date - self.start_date).days, 1)

    @property
    def current_day(self):
        delta = timezone.now().date() - self.start_date
        return max(0, min(delta.days, self.total_project_days))

    @property
    def pending_documents_count(self):
        return self.document_requests.filter(
            is_active=True, portal_visible=True, status__in=['sent', 'viewed']
        ).count()

    @property
    def missing_assets_count(self):
        return self.asset_requests.filter(
            is_active=True, portal_visible=True, required=True,
            status__in=['requested']
        ).count()

    def get_tasks_by_status(self):
        statuses = ['backlog', 'todo', 'in_progress', 'review', 'completed']
        grouped = {}
        for status in statuses:
            grouped[status] = self.tasks.filter(
                is_active=True, status=status
            ).select_related('project').prefetch_related('assigned_to')
        return grouped

    def get_portal_timeline_tasks(self):
        return self.tasks.filter(is_active=True).order_by('start_date', 'order')


# ═══════════════════════════════════════
# TASK
# ═══════════════════════════════════════

class Task(models.Model):
    STATUS_CHOICES = [
        ('backlog', 'Backlog'),
        ('todo', 'To Do'),
        ('in_progress', 'In Progress'),
        ('review', 'Review'),
        ('completed', 'Completed'),
    ]

    PRIORITY_CHOICES = [
        ('low', 'Low'),
        ('medium', 'Medium'),
        ('high', 'High'),
        ('urgent', 'Urgent'),
    ]

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tasks')
    title = models.CharField(max_length=300)
    description = models.TextField(blank=True)
    assigned_to = models.ManyToManyField(
        settings.AUTH_USER_MODEL, blank=True, related_name='assigned_tasks'
    )
    start_date = models.DateField(null=True, blank=True)
    due_date = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='backlog')
    priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
    estimated_hours = models.DecimalField(
        max_digits=6, decimal_places=1, default=0,
        validators=[MinValueValidator(0)]
    )
    progress_percentage = models.IntegerField(
        default=0,
        validators=[MinValueValidator(0), MaxValueValidator(100)]
    )
    order = models.IntegerField(default=0)
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='created_tasks'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['order', '-priority', 'due_date']

    def __str__(self):
        return f'{self.project.name} — {self.title}'

    @property
    def is_overdue(self):
        if self.due_date and self.status != 'completed':
            return self.due_date < timezone.now().date()
        return False

    @property
    def days_until_due(self):
        if not self.due_date:
            return None
        return (self.due_date - timezone.now().date()).days

    @property
    def gantt_status(self):
        if self.status == 'completed':
            return 'completed'
        elif self.is_overdue:
            return 'delayed'
        elif self.status in ('in_progress', 'review'):
            return 'in-progress'
        else:
            return 'upcoming'

    def start_day_offset(self):
        if not self.start_date:
            return 0
        return max((self.start_date - self.project.start_date).days, 0)

    def end_day_offset(self):
        if not self.due_date:
            return self.start_day_offset() + 7
        return max((self.due_date - self.project.start_date).days, 1)

    def left_percent(self):
        total = self.project.total_project_days
        if total == 0:
            return 0
        return (self.start_day_offset() / total) * 100

    def width_percent(self):
        total = self.project.total_project_days
        if total == 0:
            return 0
        duration = self.end_day_offset() - self.start_day_offset()
        return max((duration / total) * 100, 1)

    def can_transition_to(self, new_status):
        allowed = {
            'backlog': {'todo', 'in_progress', 'review', 'completed', 'backlog'},
            'todo': {'in_progress', 'review', 'completed', 'backlog', 'todo'},
            'in_progress': {'review', 'completed', 'todo', 'in_progress'},
            'review': {'completed', 'in_progress', 'todo', 'review'},
            'completed': {'review', 'completed'},
        }
        return new_status in allowed.get(self.status, set())

    def save(self, *args, **kwargs):
        if self.status == 'completed':
            self.progress_percentage = 100
        super().save(*args, **kwargs)


# ═══════════════════════════════════════
# TASK COMMENT
# ═══════════════════════════════════════

class TaskComment(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='comments')
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
        related_name='task_comments'
    )
    comment = models.TextField()
    attachment = models.FileField(upload_to='task_comments/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f"Comment by {self.user.get_full_name()} on {self.task.title}"


# ═══════════════════════════════════════
# DOCUMENT REQUEST
# ═══════════════════════════════════════

class DocumentRequest(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('sent', 'Sent to Client'),
        ('viewed', 'Viewed'),
        ('signed', 'Signed'),
        ('expired', 'Expired'),
    ]

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='document_requests')
    title = models.CharField(max_length=300)
    description = models.TextField(blank=True, help_text="Internal description")
    preview_text = models.TextField(blank=True, help_text="Text shown to client in portal")
    requires_signature = models.BooleanField(default=True)
    deadline = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    file = models.FileField(upload_to='documents/', blank=True, null=True)
    portal_visible = models.BooleanField(
        default=False,
        help_text="When True, this document appears on the client portal"
    )
    signed = models.BooleanField(default=False)
    signed_by = models.CharField(max_length=200, blank=True)
    signed_at = models.DateTimeField(blank=True, null=True)
    signed_file = models.FileField(
        upload_to='signed_documents/', blank=True, null=True,
        help_text="Signed document uploaded by client"
    )
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='created_doc_requests'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'{self.project.name} — {self.title}'

    @property
    def is_overdue(self):
        if self.deadline and not self.signed:
            return self.deadline < timezone.now().date()
        return False

    def send_to_portal(self):
        self.portal_visible = True
        self.status = 'sent'
        self.save()


# ═══════════════════════════════════════
# ASSET REQUEST
# ═══════════════════════════════════════

class AssetRequest(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('requested', 'Requested'),
        ('uploaded', 'Uploaded'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected'),
    ]

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='asset_requests')
    title = models.CharField(max_length=300)
    description = models.TextField(blank=True)
    required = models.BooleanField(default=True)
    deadline = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    portal_visible = models.BooleanField(
        default=False,
        help_text="When True, this asset request appears on the client portal"
    )
    file = models.FileField(upload_to='assets/', blank=True, null=True)
    file_name = models.CharField(max_length=300, blank=True)
    uploaded_at = models.DateTimeField(blank=True, null=True)
    is_active = models.BooleanField(default=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='created_asset_requests'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'{self.project.name} — {self.title}'

    @property
    def is_overdue(self):
        if self.deadline and self.status in ('requested',):
            return self.deadline < timezone.now().date()
        return False

    def send_to_portal(self):
        self.portal_visible = True
        self.status = 'requested'
        self.save()


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

class CompanyDocument(models.Model):
    title = models.CharField(max_length=300)
    description = models.TextField(blank=True)
    file = models.FileField(upload_to='company_documents/%Y/%m/')
    uploaded_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='uploaded_company_documents'
    )
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return self.title

    @property
    def filename(self):
        return self.file.name.split('/')[-1] if self.file else ''


# ═══════════════════════════════════════
# ACTIVITY LOG
# ═══════════════════════════════════════

class ActivityLog(models.Model):
    ACTION_CHOICES = [
        ('created', 'Created'),
        ('updated', 'Updated'),
        ('deleted', 'Deleted'),
        ('commented', 'Commented'),
        ('signed', 'Signed'),
        ('uploaded', 'Uploaded'),
        ('moved', 'Moved'),
        ('assigned', 'Assigned'),
    ]

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
        related_name='activity_logs'
    )
    action = models.CharField(max_length=20, choices=ACTION_CHOICES)
    target_type = models.CharField(max_length=50)
    target_id = models.PositiveIntegerField(null=True, blank=True)
    target_title = models.CharField(max_length=300)
    project = models.ForeignKey(
        Project, on_delete=models.CASCADE, null=True, blank=True,
        related_name='activity_logs'
    )
    details = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f"{self.user.get_full_name()} {self.action} {self.target_type}: {self.target_title}"

    @classmethod
    def log(cls, user, action, target, project=None, details=''):
        return cls.objects.create(
            user=user,
            action=action,
            target_type=target.__class__.__name__,
            target_id=target.pk,
            target_title=str(target),
            project=project,
            details=details,
        )


# ═══════════════════════════════════════
# APPROVAL REQUEST
# ═══════════════════════════════════════

class ApprovalRequest(models.Model):
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected'),
    ]

    module = models.CharField(max_length=50)
    action = models.CharField(max_length=100)
    target_type = models.CharField(max_length=100)
    target_id = models.PositiveIntegerField()
    summary = models.CharField(max_length=300)
    details = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    requested_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, related_name='approval_requests_made'
    )
    approved_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='approval_requests_approved'
    )
    approved_at = models.DateTimeField(null=True, blank=True)
    rejected_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'{self.module}:{self.action} #{self.target_id} ({self.status})'

    @classmethod
    def request_for(cls, *, module, action, target, summary, requested_by, details=''):
        return cls.objects.create(
            module=module,
            action=action,
            target_type=target.__class__.__name__,
            target_id=target.pk,
            summary=summary,
            details=details,
            requested_by=requested_by
        )

    def approve(self, user):
        self.status = 'approved'
        self.approved_by = user
        self.approved_at = timezone.now()
        self.save(update_fields=['status', 'approved_by', 'approved_at', 'updated_at'])

    def reject(self, user):
        self.status = 'rejected'
        self.approved_by = user
        self.rejected_at = timezone.now()
        self.save(update_fields=['status', 'approved_by', 'rejected_at', 'updated_at'])


# ═══════════════════════════════════════
# SYSTEM NOTIFICATION
# ═══════════════════════════════════════

class SystemNotification(models.Model):
    CATEGORY_CHOICES = [
        ('task', 'Task'),
        ('document', 'Document'),
        ('asset', 'Asset'),
        ('approval', 'Approval'),
        ('system', 'System'),
    ]

    recipient = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
        related_name='system_notifications'
    )
    category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='system')
    title = models.CharField(max_length=200)
    message = models.TextField(blank=True)
    link = models.CharField(max_length=300, blank=True)
    is_read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    read_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'{self.recipient} - {self.title}'

    def mark_read(self):
        if not self.is_read:
            self.is_read = True
            self.read_at = timezone.now()
            self.save(update_fields=['is_read', 'read_at'])

    @classmethod
    def notify(cls, *, recipient, title, message='', category='system', link=''):
        if not recipient:
            return None
        return cls.objects.create(
            recipient=recipient,
            title=title,
            message=message,
            category=category,
            link=link
        )
