"""
Models abstratos para customização
Estes models podem ser herdados nos apps clientes para adicionar campos e métodos customizados
"""

from shared_auth.conf import MEMBER_SYSTEM_GROUP_TABLE
from shared_auth.conf import GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
from shared_auth.conf import PLAN_GROUP_PERMISSIONS_TABLE
from shared_auth.conf import GROUP_ORG_PERMISSIONS_TABLE
from shared_auth.conf import SUBSCRIPTION_TABLE
from shared_auth.conf import PLAN_TABLE
from shared_auth.conf import GROUP_PERMISSIONS_TABLE
from shared_auth.conf import PERMISSION_TABLE
from shared_auth.conf import SYSTEM_TABLE
import os
from django.utils import timezone
from django.contrib.auth.models import AbstractUser
from django.db import models

from .conf import MEMBER_TABLE, ORGANIZATION_TABLE, TOKEN_TABLE, USER_TABLE
from .exceptions import OrganizationNotFoundError
from .managers import SharedMemberManager, SharedOrganizationManager, UserManager
from .storage_backend import Storage


def organization_image_path(instance, filename):
    return os.path.join(
        "organization",
        str(instance.pk),
        "images",
        filename,
    )


class AbstractSharedToken(models.Model):
    """
    Model abstrato READ-ONLY da tabela authtoken_token
    Usado para validar tokens em outros sistemas

    Para customizar, crie um model no seu app:

    from shared_auth.abstract_models import AbstractSharedToken

    class CustomToken(AbstractSharedToken):
        # Adicione campos customizados
        custom_field = models.CharField(max_length=100)

        class Meta(AbstractSharedToken.Meta):
            pass

    E configure no settings.py:
    SHARED_AUTH_TOKEN_MODEL = 'seu_app.CustomToken'
    """

    key = models.CharField(max_length=40, primary_key=True)
    user_id = models.IntegerField()
    created = models.DateTimeField()

    objects = models.Manager()

    class Meta:
        abstract = True
        managed = False
        db_table = TOKEN_TABLE

    def __str__(self):
        return self.key

    @property
    def user(self):
        """Acessa usuário do token"""
        from .utils import get_user_model

        if not hasattr(self, "_cached_user"):
            User = get_user_model()
            self._cached_user = User.objects.get_or_fail(self.user_id)
        return self._cached_user

    def is_valid(self):
        """Verifica se token ainda é válido"""
        # Implementar lógica de expiração se necessário
        return True


class AbstractSharedOrganization(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization
    Usado para acessar dados de organizações em outros sistemas

    Para customizar, crie um model no seu app:

    from shared_auth.abstract_models import AbstractSharedOrganization

    class CustomOrganization(AbstractSharedOrganization):
        # Adicione campos customizados
        custom_field = models.CharField(max_length=100)

        class Meta(AbstractSharedOrganization.Meta):
            pass

    E configure no settings.py:
    SHARED_AUTH_ORGANIZATION_MODEL = 'seu_app.CustomOrganization'
    """

    # Campos principais
    name = models.CharField(max_length=255)
    fantasy_name = models.CharField(max_length=255, blank=True, null=True)
    cnpj = models.CharField(max_length=255, blank=True, null=True)
    telephone = models.CharField(max_length=50, blank=True, null=True)
    cellphone = models.CharField(max_length=50, blank=True, null=True)
    email = models.EmailField(blank=True, null=True)
    image_organization = models.ImageField(
        storage=Storage, upload_to=organization_image_path, null=True
    )
    logo = models.ImageField(
        storage=Storage, upload_to=organization_image_path, null=True
    )

    # Relacionamentos
    main_organization_id = models.IntegerField(null=True, blank=True)
    is_branch = models.BooleanField(default=False)
    metadata = models.JSONField(default=dict)

    # Metadados
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    deleted_at = models.DateTimeField(null=True, blank=True)

    objects = SharedOrganizationManager()

    class Meta:
        abstract = True
        managed = False
        db_table = ORGANIZATION_TABLE

    def __str__(self):
        return self.fantasy_name or self.name or f"Org #{self.pk}"

    @property
    def main_organization(self):
        """
        Acessa organização principal (lazy loading)

        Usage:
            if org.is_branch:
                main = org.main_organization
        """
        from .utils import get_organization_model

        if self.main_organization_id:
            Organization = get_organization_model()
            return Organization.objects.get_or_fail(self.main_organization_id)
        return None

    @property
    def branches(self):
        """
        Retorna filiais desta organização

        Usage:
            branches = org.branches
        """
        from .utils import get_organization_model

        Organization = get_organization_model()
        return Organization.objects.filter(main_organization_id=self.pk)

    @property
    def members(self):
        """
        Retorna membros desta organização

        Usage:
            members = org.members
            for member in members:
                print(member.user.email)
        """
        from .utils import get_member_model

        Member = get_member_model()
        return Member.objects.for_organization(self.pk)

    @property
    def users(self):
        """
        Retorna usuários desta organização

        Usage:
            users = org.users
        """
        from .utils import get_user_model

        User = get_user_model()
        return User.objects.filter(
            id__in=self.members.values_list("user_id", flat=True)
        )

    def is_active(self):
        """Verifica se organização está ativa"""
        return self.deleted_at is None


class AbstractUser(AbstractUser):
    """
    Model abstrato READ-ONLY da tabela auth_user

    Para customizar, crie um model no seu app:

    from shared_auth.abstract_models import AbstractUser

    class CustomUser(AbstractUser):
        # Adicione campos customizados
        custom_field = models.CharField(max_length=100)

        class Meta(AbstractUser.Meta):
            pass

    E configure no settings.py:
    SHARED_AUTH_USER_MODEL = 'seu_app.CustomUser'
    """

    date_joined = models.DateTimeField()
    last_login = models.DateTimeField(null=True, blank=True)
    avatar = models.ImageField(storage=Storage, blank=True, null=True)

    # Campos customizados
    createdat = models.DateTimeField()
    updatedat = models.DateTimeField()
    deleted_at = models.DateTimeField(null=True, blank=True)

    objects = UserManager()

    class Meta:
        abstract = True
        managed = False
        db_table = USER_TABLE

    @property
    def organizations(self):
        """
        Retorna todas as organizações associadas ao usuário.
        """
        from .utils import get_member_model, get_organization_model

        Organization = get_organization_model()
        Member = get_member_model()

        return Organization.objects.filter(
            id__in=Member.objects.filter(user_id=self.id).values_list(
                "organization_id", flat=True
            )
        )

    def get_org(self, organization_id):
        """
        Retorna a organização especificada pelo ID, se o usuário for membro.
        """
        from .utils import get_member_model, get_organization_model

        Organization = get_organization_model()
        Member = get_member_model()

        try:
            organization = Organization.objects.get(id=organization_id)
        except Organization.DoesNotExist:
            raise OrganizationNotFoundError(
                f"Organização com ID {organization_id} não encontrada."
            )

        if not Member.objects.filter(
            user_id=self.id, organization_id=organization.id
        ).exists():
            raise OrganizationNotFoundError("Usuário não é membro desta organização.")

        return organization


class AbstractSharedMember(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization_member
    Relacionamento entre User e Organization

    Para customizar, crie um model no seu app:

    from shared_auth.abstract_models import AbstractSharedMember

    class CustomMember(AbstractSharedMember):
        # Adicione campos customizados
        custom_field = models.CharField(max_length=100)

        class Meta(AbstractSharedMember.Meta):
            pass

    E configure no settings.py:
    SHARED_AUTH_MEMBER_MODEL = 'seu_app.CustomMember'
    """

    user_id = models.IntegerField()
    organization_id = models.IntegerField()
    metadata = models.JSONField(default=dict)

    objects = SharedMemberManager()

    class Meta:
        abstract = True
        managed = False
        db_table = MEMBER_TABLE

    def __str__(self):
        return f"Member: User {self.user_id} - Org {self.organization_id}"

    @property
    def user(self):
        """
        Acessa usuário (lazy loading)

        Usage:
            member = SharedMember.objects.first()
            user = member.user
            print(user.email)
        """
        from .utils import get_user_model

        User = get_user_model()
        return User.objects.get_or_fail(self.user_id)

    @property
    def organization(self):
        """
        Acessa organização (lazy loading)

        Usage:
            member = SharedMember.objects.first()
            org = member.organization
            print(org.name)
        """
        from .utils import get_organization_model

        Organization = get_organization_model()
        return Organization.objects.get_or_fail(self.organization_id)

class GroupPermissionsPermission(models.Model):
    grouppermissions_id = models.BigIntegerField()
    permissions_id = models.BigIntegerField()

    class Meta:
        db_table = GROUP_PERMISSIONS_PERMISSIONS_TABLE
        managed = False
        unique_together = ('grouppermissions_id', 'permissions_id')
        app_label = 'shared_auth'

    def __str__(self):
        return f"GroupPerm {self.grouppermissions_id} → Perm {self.permissions_id}"


class PlanGroupPermission(models.Model):
    plan_id = models.BigIntegerField()
    grouppermissions_id = models.BigIntegerField()

    class Meta:
        db_table = PLAN_GROUP_PERMISSIONS_TABLE
        managed = False
        unique_together = ('plan_id', 'grouppermissions_id')
        app_label = 'shared_auth'

    def __str__(self):
        return f"Plan {self.plan_id} → GroupPerm {self.grouppermissions_id}"

class GroupOrgPermissionsPermission(models.Model):
    grouporganizationpermissions_id = models.BigIntegerField()
    permissions_id = models.BigIntegerField()

    class Meta:
        db_table = GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
        managed = False
        unique_together = ('grouporganizationpermissions_id', 'permissions_id')
        app_label = 'shared_auth'

    def __str__(self):
        return f"OrgGroup {self.grouporganizationpermissions_id} → Perm {self.permissions_id}"

class MemberSystemGroup(models.Model):
    member_id = models.BigIntegerField()
    group_id = models.BigIntegerField()
    system_id = models.BigIntegerField()
    created_at = models.DateTimeField()

    class Meta:
        db_table = MEMBER_SYSTEM_GROUP_TABLE
        managed = False
        unique_together = ('member_id', 'group_id', 'system_id')
        app_label = 'shared_auth'

    def __str__(self):
        return f"Member {self.member_id} → Group {self.group_id} (System {self.system_id})"

class AbstractSystem(models.Model):
    """
    Model abstrato READ-ONLY da tabela plans_system
    Representa um sistema externo que usa este serviço de autenticação
    
    Para customizar, crie um model no seu app:
    
    from shared_auth.abstract_models import AbstractSystem
    
    class CustomSystem(AbstractSystem):
        custom_field = models.CharField(max_length=100)
        
        class Meta(AbstractSystem.Meta):
            pass
    
    E configure no settings.py:
    SHARED_AUTH_SYSTEM_MODEL = 'seu_app.CustomSystem'
    """
    
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    active = models.BooleanField(default=True)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    
    objects = models.Manager()  # Will be replaced by SystemManager in concrete model
    
    class Meta:
        abstract = True
        managed = False
        db_table = SYSTEM_TABLE
    
    def __str__(self):
        return self.name


class AbstractPermission(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization_permissions
    Define permissões específicas de cada sistema
    
    Para customizar, crie um model no seu app e configure:
    SHARED_AUTH_PERMISSION_MODEL = 'seu_app.CustomPermission'
    """
    
    codename = models.CharField(max_length=100)
    name = models.CharField(max_length=100)
    description = models.TextField()
    scope = models.CharField(
        max_length=100,
        help_text="Agrupamento da permissão (ex: 'Recebimentos', 'Pagamentos', 'Relatórios')",
        blank=True,
        default=""
    )
    system_id = models.IntegerField()
    
    objects = models.Manager()
    
    class Meta:
        abstract = True
        managed = False
        db_table = PERMISSION_TABLE
    
    def __str__(self):
        return f"{self.codename} ({self.name})"
    
    @property
    def system(self):
        """Acessa sistema (lazy loading)"""
        from .utils import get_system_model
        
        if not hasattr(self, "_cached_system"):
            System = get_system_model()
            self._cached_system = System.objects.get_or_fail(self.system_id)
        return self._cached_system


class AbstractGroupPermissions(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization_grouppermissions
    Grupos base de permissões (usados nos planos)
    
    Para customizar, configure:
    SHARED_AUTH_GROUP_PERMISSIONS_MODEL = 'seu_app.CustomGroupPermissions'
    """
    
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    system_id = models.IntegerField()
    
    objects = models.Manager()
    
    class Meta:
        abstract = True
        managed = False
        db_table = GROUP_PERMISSIONS_TABLE
    
    def __str__(self):
        return self.name
    
    @property
    def system(self):
        """Acessa sistema (lazy loading)"""
        from .utils import get_system_model
        
        if not hasattr(self, "_cached_system"):
            System = get_system_model()
            self._cached_system = System.objects.get_or_fail(self.system_id)
        return self._cached_system
    
    @property
    def permissions(self):
        from .utils import get_permission_model

        Permission = get_permission_model()

        perm_ids = GroupPermissionsPermission.objects.using('auth_db') \
            .filter(grouppermissions_id=self.pk) \
            .values_list('permissions_id', flat=True)

        return Permission.objects.using('auth_db').filter(id__in=perm_ids)


class AbstractPlan(models.Model):
    """
    Model abstrato READ-ONLY da tabela plans_plan
    Planos oferecidos por cada sistema, com conjunto de permissões
    
    Para customizar, configure:
    SHARED_AUTH_PLAN_MODEL = 'seu_app.CustomPlan'
    """
    
    name = models.CharField(max_length=100)
    slug = models.SlugField()
    system_id = models.IntegerField()
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    active = models.BooleanField(default=True)
    recurrence = models.CharField(max_length=10)
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    
    objects = models.Manager()
    
    class Meta:
        abstract = True
        managed = False
        db_table = PLAN_TABLE
    
    def __str__(self):
        return f"{self.name} - {self.system.name if hasattr(self, 'system') else self.system_id}"
    
    @property
    def system(self):
        """Acessa sistema (lazy loading)"""
        from .utils import get_system_model
        
        if not hasattr(self, "_cached_system"):
            System = get_system_model()
            self._cached_system = System.objects.get_or_fail(self.system_id)
        return self._cached_system
    
    @property
    def group_permissions(self):
        from .utils import get_group_permissions_model
        from .models import PlanGroupPermission

        GroupPermissions = get_group_permissions_model()

        group_ids = PlanGroupPermission.objects.using('auth_db') \
            .filter(plan_id=self.pk) \
            .values_list('grouppermissions_id', flat=True)

        return GroupPermissions.objects.using('auth_db').filter(id__in=group_ids)


class AbstractSubscription(models.Model):
    """
    Model abstrato READ-ONLY da tabela plans_subscription
    Assinatura de uma organização a um plano
    
    Para customizar, configure:
    SHARED_AUTH_SUBSCRIPTION_MODEL = 'seu_app.CustomSubscription'
    """
    
    organization_id = models.IntegerField()
    plan_id = models.IntegerField()
    period = models.CharField(max_length=20)
    payment_date = models.DateTimeField(null=True, blank=True)
    paid = models.BooleanField(default=False)
    active = models.BooleanField(default=True)
    started_at = models.DateTimeField()
    expires_at = models.DateTimeField(null=True, blank=True)
    
    objects = models.Manager()  # Will be replaced by SubscriptionManager
    
    class Meta:
        abstract = True
        managed = False
        db_table = SUBSCRIPTION_TABLE
    
    def __str__(self):
        return f"Subscription {self.pk} - Org {self.organization_id}"
    
    @property
    def organization(self):
        """Acessa organização (lazy loading)"""
        from .utils import get_organization_model
        
        if not hasattr(self, "_cached_organization"):
            Organization = get_organization_model()
            self._cached_organization = Organization.objects.get_or_fail(self.organization_id)
        return self._cached_organization
    
    @property
    def plan(self):
        """Acessa plano (lazy loading)"""
        from .utils import get_plan_model
        
        if not hasattr(self, "_cached_plan"):
            Plan = get_plan_model()
            try:
                self._cached_plan = Plan.objects.get(pk=self.plan_id)
            except Plan.DoesNotExist:
                self._cached_plan = None
        return self._cached_plan
    
    def is_valid(self):
        """Verifica se assinatura está ativa, paga e não expirada"""
        from django.utils import timezone
        
        if not self.active or not self.paid:
            return False
        
        if self.expires_at and self.expires_at < timezone.now():
            return False
        
        return True
    
    def set_paid(self):
        """Marca assinatura como paga (apenas para referência, não salva)"""
        self.paid = True
        self.payment_date = timezone.now()
    
    def cancel(self):
        """Cancela assinatura (apenas para referência, não salva)"""
        self.active = False


class AbstractGroupOrganizationPermissions(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization_grouporganizationpermissions
    Grupos de permissões criados pela organização para distribuir aos usuários
    
    Para customizar, configure:
    SHARED_AUTH_GROUP_ORG_PERMISSIONS_MODEL = 'seu_app.CustomGroupOrgPermissions'
    """
    
    organization_id = models.IntegerField()
    system_id = models.IntegerField()
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    
    objects = models.Manager()  # Will be replaced by GroupOrganizationPermissionsManager
    
    class Meta:
        abstract = True
        managed = False
        db_table = GROUP_ORG_PERMISSIONS_TABLE
    
    def __str__(self):
        return f"{self.name} (Org {self.organization_id})"
    
    @property
    def organization(self):
        """Acessa organização (lazy loading)"""
        from .utils import get_organization_model
        
        if not hasattr(self, "_cached_organization"):
            Organization = get_organization_model()
            self._cached_organization = Organization.objects.get_or_fail(self.organization_id)
        return self._cached_organization
    
    @property
    def system(self):
        """Acessa sistema (lazy loading)"""
        from .utils import get_system_model
        
        if not hasattr(self, "_cached_system"):
            System = get_system_model()
            self._cached_system = System.objects.get_or_fail(self.system_id)
        return self._cached_system
    
    @property
    def permissions(self):
        from .utils import get_permission_model
        from .models import GroupOrgPermissionsPermission

        Permission = get_permission_model()

        perm_ids = GroupOrgPermissionsPermission.objects.using('auth_db') \
            .filter(grouporganizationpermissions_id=self.pk) \
            .values_list('permissions_id', flat=True)

        return Permission.objects.using('auth_db').filter(id__in=perm_ids)


class AbstractMemberSystemGroup(models.Model):
    """
    Model abstrato READ-ONLY da tabela organization_membersystemgroup
    Relaciona um membro a um grupo de permissões em um sistema específico
    
    Para customizar, configure:
    SHARED_AUTH_MEMBER_SYSTEM_GROUP_MODEL = 'seu_app.CustomMemberSystemGroup'
    """
    
    member_id = models.IntegerField()
    group_id = models.IntegerField()
    system_id = models.IntegerField()
    created_at = models.DateTimeField()
    
    objects = models.Manager()  # Will be replaced by MemberSystemGroupManager
    
    class Meta:
        abstract = True
        managed = False
        db_table = GROUP_ORG_PERMISSIONS_TABLE
    
    def __str__(self):
        return f"Member {self.member_id} - Group {self.group_id} - System {self.system_id}"
    
    @property
    def member(self):
        """Acessa membro (lazy loading)"""
        from .utils import get_member_model
        
        if not hasattr(self, "_cached_member"):
            Member = get_member_model()
            try:
                self._cached_member = Member.objects.get(pk=self.member_id)
            except Member.DoesNotExist:
                self._cached_member = None
        return self._cached_member
    
    @property
    def group(self):
        """Acessa grupo (lazy loading)"""
        from .utils import get_group_organization_permissions_model
        
        if not hasattr(self, "_cached_group"):
            GroupOrgPermissions = get_group_organization_permissions_model()
            try:
                self._cached_group = GroupOrgPermissions.objects.get(pk=self.group_id)
            except GroupOrgPermissions.DoesNotExist:
                self._cached_group = None
        return self._cached_group
    
    @property
    def system(self):
        """Acessa sistema (lazy loading)"""
        from .utils import get_system_model
        
        if not hasattr(self, "_cached_system"):
            System = get_system_model()
            self._cached_system = System.objects.get_or_fail(self.system_id)
        return self._cached_system

