"""
Organization Models - Organizational Units with hierarchy support.

This module provides a generic organizational structure that can be used
by all modules in the platform for:
- Permission scoping (access to specific units)
- Data filtering (records belonging to units)
- Reporting (aggregation by organizational hierarchy)
"""
from django.db import models
from django.core.validators import RegexValidator
from django.utils.translation import gettext_lazy as _

from apps.core.tenant.models import TenantAwareModel
from .managers import OrganizationalUnitManager, UserOrganizationalUnitManager


class OrganizationalUnitType(models.TextChoices):
    """
    Predefined organizational unit types.
    Modules can extend this with custom types via metadata.
    """
    COMPANY = 'COMPANY', 'Company'
    BRANCH = 'BRANCH', 'Branch/Location'
    DIVISION = 'DIVISION', 'Division'
    DEPARTMENT = 'DEPARTMENT', 'Department'
    TEAM = 'TEAM', 'Team'
    PROJECT = 'PROJECT', 'Project'
    COST_CENTER = 'COST_CENTER', 'Cost Center'
    CUSTOM = 'CUSTOM', 'Custom'


class OrganizationalUnit(TenantAwareModel):
    """
    Generic Organizational Unit model.
    
    Represents any organizational structure (branch, department, team, etc.)
    with support for:
    - Hierarchical relationships (parent/children)
    - Materialized path for efficient tree queries
    - Flexible metadata for module-specific extensions
    
    Usage Examples:
    - Branch: type=BRANCH, parent=company
    - Department: type=DEPARTMENT, parent=branch
    - Team: type=TEAM, parent=department
    
    All modules use this for permission scoping and data filtering
    without dependency on HRM module.
    """
    
    # Basic Information
    name = models.CharField(
        max_length=255,
        help_text="Display name of the organizational unit"
    )
    code = models.CharField(
        max_length=50,
        validators=[RegexValidator(
            regex=r'^[A-Z0-9_-]+$',
            message='Code must be uppercase alphanumeric with underscores or hyphens'
        )],
        help_text="Unique code within tenant (e.g., 'HQ', 'SALES-01', 'IT-DEV')"
    )
    description = models.TextField(
        blank=True,
        help_text="Optional description"
    )
    
    # Type
    unit_type = models.CharField(
        max_length=50,
        choices=OrganizationalUnitType.choices,
        default=OrganizationalUnitType.DEPARTMENT,
        db_index=True,
        help_text="Type of organizational unit"
    )
    custom_type_name = models.CharField(
        max_length=100,
        blank=True,
        help_text="Custom type name when unit_type is CUSTOM"
    )
    
    # Hierarchy
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='children',
        help_text="Parent organizational unit"
    )
    
    # Materialized Path for efficient tree queries
    # Format: "/uuid1/uuid2/uuid3/" where each uuid is an ancestor
    path = models.CharField(
        max_length=1000,
        blank=True,
        db_index=True,
        help_text="Materialized path for tree queries"
    )
    level = models.PositiveIntegerField(
        default=0,
        db_index=True,
        help_text="Depth level in hierarchy (0 = root)"
    )
    
    # Ordering within siblings
    sort_order = models.PositiveIntegerField(
        default=0,
        help_text="Sort order among siblings"
    )
    
    # Status
    is_active = models.BooleanField(
        default=True,
        db_index=True,
        help_text="Whether this unit is active"
    )
    
    # Metadata for module-specific extensions
    # HRM can store: manager_id, headcount_limit
    # Accounting can store: cost_center_code, budget
    metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text="Module-specific metadata (JSON)"
    )
    
    # Contact Information (optional)
    address = models.TextField(blank=True)
    phone = models.CharField(max_length=50, blank=True)
    email = models.EmailField(blank=True)
    
    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    # Manager
    objects = OrganizationalUnitManager()
    
    class Meta:
        db_table = 'organizational_units'
        verbose_name = 'Organizational Unit'
        verbose_name_plural = 'Organizational Units'
        unique_together = ['tenant', 'code']
        ordering = ['path', 'sort_order', 'name']
        indexes = [
            models.Index(fields=['tenant', 'unit_type']),
            models.Index(fields=['tenant', 'is_active']),
            models.Index(fields=['tenant', 'parent']),
            models.Index(fields=['path']),
        ]
    
    def __str__(self):
        return f"{self.name} ({self.code})"
    
    def save(self, *args, **kwargs):
        """Update path and level before saving."""
        self._update_path()
        super().save(*args, **kwargs)
        # Update children paths if this unit moved
        self._update_children_paths()
    
    def _update_path(self):
        """Calculate materialized path and level."""
        if self.parent:
            self.path = f"{self.parent.path}{self.parent.pk}/"
            self.level = self.parent.level + 1
        else:
            self.path = "/"
            self.level = 0
    
    def _update_children_paths(self):
        """Recursively update children paths when parent changes."""
        for child in self.children.all():
            child.save()  # This will trigger _update_path for each child
    
    @property
    def full_path_names(self) -> str:
        """Get full path as names (e.g., 'Company > Branch > Department')."""
        ancestors = self.get_ancestors()
        names = [a.name for a in ancestors] + [self.name]
        return ' > '.join(names)
    
    def get_ancestors(self, include_self: bool = False):
        """Get all ancestors ordered from root to immediate parent."""
        if not self.path or self.path == "/":
            return [self] if include_self else []
        
        # Extract UUIDs from path
        ancestor_ids = [
            uuid for uuid in self.path.strip('/').split('/') 
            if uuid
        ]
        
        if not ancestor_ids:
            return [self] if include_self else []
        
        ancestors = list(
            OrganizationalUnit.objects.filter(pk__in=ancestor_ids)
            .order_by('level')
        )
        
        if include_self:
            ancestors.append(self)
        
        return ancestors
    
    def get_descendants(self, include_self: bool = False):
        """Get all descendants."""
        path_prefix = f"{self.path}{self.pk}/"
        descendants = OrganizationalUnit.objects.filter(
            tenant=self.tenant,
            path__startswith=path_prefix
        ).order_by('path', 'sort_order')
        
        if include_self:
            from itertools import chain
            return list(chain([self], descendants))
        
        return list(descendants)
    
    def get_children(self):
        """Get direct children."""
        return self.children.filter(is_active=True).order_by('sort_order', 'name')
    
    def get_siblings(self, include_self: bool = False):
        """Get siblings (same parent)."""
        siblings = OrganizationalUnit.objects.filter(
            tenant=self.tenant,
            parent=self.parent
        ).order_by('sort_order', 'name')
        
        if not include_self:
            siblings = siblings.exclude(pk=self.pk)
        
        return siblings
    
    def is_ancestor_of(self, other: 'OrganizationalUnit') -> bool:
        """Check if this unit is an ancestor of another."""
        if not other.path:
            return False
        return str(self.pk) in other.path
    
    def is_descendant_of(self, other: 'OrganizationalUnit') -> bool:
        """Check if this unit is a descendant of another."""
        return other.is_ancestor_of(self)
    
    def move_to(self, new_parent: 'OrganizationalUnit' = None):
        """Move this unit to a new parent."""
        if new_parent and new_parent.is_descendant_of(self):
            raise ValueError("Cannot move a unit to its own descendant")
        
        self.parent = new_parent
        self.save()


class UserOrganizationalUnit(models.Model):
    """
    Many-to-many relationship between User and OrganizationalUnit.
    
    Defines which organizational units a user belongs to or has access to.
    Users can have different roles in different units.
    """
    user = models.ForeignKey(
        'core_auth.User',
        on_delete=models.CASCADE,
        related_name='organizational_units'
    )
    organizational_unit = models.ForeignKey(
        OrganizationalUnit,
        on_delete=models.CASCADE,
        related_name='user_memberships'
    )
    
    # Membership type
    is_primary = models.BooleanField(
        default=False,
        help_text="Is this the user's primary organizational unit?"
    )
    is_manager = models.BooleanField(
        default=False,
        help_text="Is the user a manager of this unit?"
    )
    
    # Access scope
    include_descendants = models.BooleanField(
        default=False,
        help_text="Does user have access to descendant units?"
    )
    
    # Date range (optional - for temporary assignments)
    start_date = models.DateField(
        null=True,
        blank=True,
        help_text="When user joined this unit"
    )
    end_date = models.DateField(
        null=True,
        blank=True,
        help_text="When user left/will leave this unit"
    )
    
    # Metadata
    metadata = models.JSONField(
        default=dict,
        blank=True
    )
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    # Manager
    objects = UserOrganizationalUnitManager()
    
    class Meta:
        db_table = 'user_organizational_units'
        verbose_name = 'User Organizational Unit'
        verbose_name_plural = 'User Organizational Units'
        unique_together = ['user', 'organizational_unit']
    
    def __str__(self):
        return f"{self.user} - {self.organizational_unit}"


# ─── Company ─────────────────────────────────────────────────────────────


class Company(TenantAwareModel):
    """
    شرکت / شخصیت حقوقی.

    مدل مستقل برای ثبت شرکت‌ها با اطلاعات حقوقی و مالی.
    هر شرکت می‌تواند به یک OrganizationalUnit از نوع COMPANY لینک شود.
    """

    name = models.CharField(_("نام شرکت"), max_length=255)
    name_en = models.CharField(_("نام انگلیسی"), max_length=255, blank=True)
    code = models.CharField(
        _("کد شرکت"),
        max_length=50,
        validators=[RegexValidator(
            regex=r'^[A-Z0-9_-]+$',
            message='Code must be uppercase alphanumeric with underscores or hyphens'
        )],
    )

    # Legal
    registration_number = models.CharField(
        _("شماره ثبت"), max_length=50, blank=True, db_index=True
    )
    national_id = models.CharField(
        _("شناسه ملی"), max_length=20, blank=True, db_index=True
    )
    economic_code = models.CharField(
        _("کد اقتصادی"), max_length=20, blank=True
    )

    # Org-unit link (optional)
    org_unit = models.OneToOneField(
        OrganizationalUnit,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="company",
        verbose_name=_("واحد سازمانی"),
    )

    # Hierarchy
    parent_company = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="subsidiaries",
        verbose_name=_("شرکت مادر"),
    )

    # Currency
    base_currency = models.ForeignKey(
        "core_currency.Currency",
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        related_name="companies",
        verbose_name=_("ارز پایه"),
    )

    # Contact
    address = models.TextField(_("آدرس"), blank=True)
    city = models.CharField(_("شهر"), max_length=100, blank=True)
    province = models.CharField(_("استان"), max_length=100, blank=True)
    country = models.CharField(_("کشور"), max_length=5, default="IR")
    postal_code = models.CharField(_("کد پستی"), max_length=20, blank=True)
    phone = models.CharField(_("تلفن"), max_length=50, blank=True)
    fax = models.CharField(_("فکس"), max_length=50, blank=True)
    email = models.EmailField(_("ایمیل"), blank=True)
    website = models.URLField(_("وبسایت"), blank=True)

    # Status
    is_active = models.BooleanField(_("فعال"), default=True, db_index=True)

    # Metadata
    metadata = models.JSONField(_("متادیتا"), default=dict, blank=True)

    # Audit
    created_at = models.DateTimeField(_("تاریخ ایجاد"), auto_now_add=True)
    updated_at = models.DateTimeField(_("تاریخ بروزرسانی"), auto_now=True)
    created_by = models.UUIDField(_("ایجاد توسط"), null=True, blank=True)
    updated_by = models.UUIDField(_("بروزرسانی توسط"), null=True, blank=True)

    class Meta:
        db_table = "companies"
        verbose_name = _("شرکت")
        verbose_name_plural = _("شرکت‌ها")
        unique_together = [["tenant", "code"]]
        ordering = ["name"]
        indexes = [
            models.Index(fields=["tenant", "is_active"]),
            models.Index(fields=["tenant", "code"]),
        ]

    def __str__(self):
        return f"{self.name} ({self.code})"


# ─── Business Partner ────────────────────────────────────────────────────


class BusinessPartnerType(models.TextChoices):
    CUSTOMER = "CUSTOMER", _("مشتری")
    VENDOR = "VENDOR", _("تأمین‌کننده")
    BOTH = "BOTH", _("مشتری و تأمین‌کننده")


class BusinessPartner(TenantAwareModel):
    """
    شریک تجاری (مشتری / تأمین‌کننده).

    مدل مرکزی پلتفرم برای استفاده تمام ماژول‌ها.
    """

    code = models.CharField(
        _("کد"),
        max_length=50,
        validators=[RegexValidator(
            regex=r'^[A-Z0-9_-]+$',
            message='Code must be uppercase alphanumeric with underscores or hyphens'
        )],
    )
    name = models.CharField(_("نام"), max_length=255)
    name_en = models.CharField(_("نام انگلیسی"), max_length=255, blank=True)
    partner_type = models.CharField(
        _("نوع"),
        max_length=20,
        choices=BusinessPartnerType.choices,
        default=BusinessPartnerType.VENDOR,
        db_index=True,
    )

    # Legal
    national_id = models.CharField(
        _("شناسه ملی / کد ملی"), max_length=20, blank=True, db_index=True
    )
    economic_code = models.CharField(
        _("کد اقتصادی"), max_length=20, blank=True
    )
    tax_id = models.CharField(
        _("شناسه مالیاتی"), max_length=50, blank=True
    )

    # Company link
    company = models.ForeignKey(
        Company,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="business_partners",
        verbose_name=_("شرکت"),
        help_text=_("شرکتی که این شریک تجاری مربوط به آن است"),
    )

    # Contact
    contact_person = models.CharField(_("نام مسئول"), max_length=255, blank=True)
    phone = models.CharField(_("تلفن"), max_length=50, blank=True)
    mobile = models.CharField(_("موبایل"), max_length=20, blank=True)
    fax = models.CharField(_("فکس"), max_length=50, blank=True)
    email = models.EmailField(_("ایمیل"), blank=True)
    website = models.URLField(_("وبسایت"), blank=True)

    # Address
    address = models.TextField(_("آدرس"), blank=True)
    city = models.CharField(_("شهر"), max_length=100, blank=True)
    province = models.CharField(_("استان"), max_length=100, blank=True)
    country = models.CharField(_("کشور"), max_length=5, default="IR")
    postal_code = models.CharField(_("کد پستی"), max_length=20, blank=True)

    # Financial
    payment_terms_days = models.PositiveIntegerField(
        _("شرایط پرداخت (روز)"), default=0
    )
    credit_limit = models.DecimalField(
        _("سقف اعتبار"),
        max_digits=18,
        decimal_places=0,
        default=0,
    )
    currency = models.ForeignKey(
        "core_currency.Currency",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="business_partners",
        verbose_name=_("ارز"),
    )
    bank_name = models.CharField(_("نام بانک"), max_length=100, blank=True)
    bank_account = models.CharField(_("شماره حساب"), max_length=50, blank=True)
    sheba = models.CharField(_("شماره شبا"), max_length=30, blank=True)

    # Status
    is_active = models.BooleanField(_("فعال"), default=True, db_index=True)

    # Metadata
    metadata = models.JSONField(_("متادیتا"), default=dict, blank=True)

    # Audit
    created_at = models.DateTimeField(_("تاریخ ایجاد"), auto_now_add=True)
    updated_at = models.DateTimeField(_("تاریخ بروزرسانی"), auto_now=True)
    created_by = models.UUIDField(_("ایجاد توسط"), null=True, blank=True)
    updated_by = models.UUIDField(_("بروزرسانی توسط"), null=True, blank=True)

    class Meta:
        db_table = "business_partners"
        verbose_name = _("شریک تجاری")
        verbose_name_plural = _("شرکای تجاری")
        unique_together = [["tenant", "code"]]
        ordering = ["name"]
        indexes = [
            models.Index(fields=["tenant", "is_active"]),
            models.Index(fields=["tenant", "partner_type"]),
            models.Index(fields=["tenant", "code"]),
        ]

    def __str__(self):
        return f"{self.name} ({self.code})"

    @property
    def is_customer(self) -> bool:
        return self.partner_type in (
            BusinessPartnerType.CUSTOMER,
            BusinessPartnerType.BOTH,
        )

    @property
    def is_vendor(self) -> bool:
        return self.partner_type in (
            BusinessPartnerType.VENDOR,
            BusinessPartnerType.BOTH,
        )
