class AccessToken(models.Model):
    """
    denotes single access token of (user, service) pair
    - tokenid:     unique token id
    - user:        user object
    - service:     service object
    - expire_time: expire time
    """
    tokenid = models.CharField(max_length=20, primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    service = models.ForeignKey(Service, on_delete=models.CASCADE)
    expire_time = models.DateTimeField()

    def __str__(self):
        return f'{self.user} - {self.service}' class Document(models.Model):
    """
    denotes single documents that used by terms and privacy policy
    - category:     terms / privacy policy
    - version:      simple version; v1.x
    - date_apply:   apply date
    - date_version: detail version; date format
    - text:         doc content; special markdown
    """
    category = models.CharField(max_length=20)
    version = models.CharField(max_length=20)
    date_apply = models.DateTimeField()
    date_version = models.DateTimeField()
    text = models.TextField()

    class Meta:
        unique_together = ('category', 'version')

    def to_html(self):
        lines = map(lambda x: x.strip(), self.text.split('\n'))
        depth = 1
        result = []
        for line in lines:
            cur_depth = line.find(' ')
            diff = cur_depth - depth
            if diff > 0:
                result.append('<ol>' * diff)
            elif diff < 0:
                result.append('</ol>' * abs(diff))
            depth = cur_depth
            if depth == 1:
                result.append(f'<h3>{line[depth:]}</h3>')
            else:
                result.append(f'<li>{line[depth:]}</li>')
        result.append('</ol>' * depth)
        return '\n'.join(result)

    def __str__(self):
        return f'{self.category} - {self.version}' class EmailAuthToken(models.Model):
    """
    denotes single email auth token for an user
    - tokenid:     unique token id
    - user:        user object
    - expire_time: expire time; < 24 hours
    """
    tokenid = models.CharField(max_length=48, primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    expire_time = models.DateTimeField()

    def __str__(self):
        return f'{self.user} - {self.tokenid}' class EmailDomain(models.Model):
    """
    denotes an email domain
    - domain:    the email domain
    - is_banned: banned status
    """
    domain = models.CharField(max_length=100, unique=True)
    is_banned = models.BooleanField(default=True) class InquiryMail(models.Model):
    """
    denotes contact mail sent to sso@sparcs.org
    - user:       User object
    - name:       name in contact form
    - topic:      topic; bugs / suggestions / accounts / etc
    - title:      title in contact form
    - content:    message content
    - created_at: creation datetime
    """
    BUG_REPORT = 'bugs'
    SUGGESTIONS = 'suggestions'
    ACCOUNTS = 'accounts'
    ETC = 'etc'
    TOPIC = (
        (BUG_REPORT, 'Bug Report'),
        (SUGGESTIONS, 'Suggestions'),
        (ACCOUNTS, 'Accounts'),
        (ETC, 'etc'),
    )
    created_at = models.DateTimeField(null=False, default=timezone.now)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='inquiry_mail', blank=True, null=True)
    name = models.CharField(max_length=40)
    email = models.EmailField(blank=True, null=True)
    topic = models.CharField(max_length=11, choices=TOPIC, default=ETC)
    title = models.CharField(max_length=100)
    content = models.TextField() class Notice(models.Model):
    """
    denotes single notice
    - title:      notice title
    - valid_from: display start time
    - valid_to:   display end time
    - text:       notice content; html
    """
    title = models.CharField(max_length=100)
    valid_from = models.DateTimeField()
    valid_to = models.DateTimeField()
    text = models.TextField()

    def to_dict(self):
        return {
            'valid_from': self.valid_from.isoformat(),
            'valid_to': self.valid_to.isoformat(),
            'title': self.title,
            'text': self.text,
        }

    def __str__(self):
        return self.title class PointLog(models.Model):
    """
    denotes single point log for a user
    - user:    user object
    - service: service object
    - time:    event time
    - delta:   change of point
    - point:   total point after delta
    - action:  detail log message
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE,
                             related_name='point_logs')
    service = models.ForeignKey(Service, on_delete=models.CASCADE)
    time = models.DateTimeField(auto_now=True)
    delta = models.IntegerField()
    point = models.IntegerField()
    action = models.CharField(max_length=200)

    def __str__(self):
        return f'{self.user} / {self.service} - {self.delta}' class ResetPWToken(models.Model):
    """
    denotes single password reset token for an user
    - tokenid:     unique token id
    - user:        user object
    - expire_time: expire time; < 24 hours
    """
    tokenid = models.CharField(max_length=48, primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    expire_time = models.DateTimeField()

    def __str__(self):
        return f'{self.user} - {self.tokenid}' class Service(models.Model):
    """
    denotes single sso client
    - name:               unique name
    - is_shown:           decides to show in SSO main page
    - alias:              user friendly name
    - scope:              scope; TEST / SPARCS / PUBLIC
    - main_url:           main page url
    - login_callback_url: login callback url
    - unregister_url:     unregister page url
    - secret_key:         secret key
    - admin_user:         admin of this client
    - cooltime:           cooltime (days) to re-register; < 60
    - icon:               icon of this service
    """

    name = models.CharField(max_length=40, primary_key=True)
    is_shown = models.BooleanField(default=True)
    alias = models.CharField(max_length=30)
    scope = models.CharField(max_length=6, choices=SERVICE_SCOPE,
                             default=SERVICE_TEST)
    main_url = models.CharField(max_length=200)
    login_callback_url = models.CharField(max_length=200)
    unregister_url = models.CharField(max_length=200, null=True, blank=True)
    secret_key = models.CharField(max_length=100)
    admin_user = models.ForeignKey(User, on_delete=models.CASCADE,
                                   related_name='managed_services')
    cooltime = models.IntegerField()
    icon = models.ImageField(null=True, blank=True)

    @property
    def icon_url(self):
        return self.icon.url if self.icon else '/static/img/test-service.png'

    def __str__(self):
        return self.alias class ServiceMap(models.Model):
    """
    denotes single (user, service) pair
    - sid:             unique mapping id
    - user:            user object
    - service:         service object
    - register_time:   register time
    - unregister_time: unregister time
    """
    sid = models.CharField(max_length=20, primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE,
                             related_name='services')
    service = models.ForeignKey(Service, on_delete=models.CASCADE)
    register_time = models.DateTimeField()
    unregister_time = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return f'{self.user} - {self.service}' class Statistic(models.Model):
    """
    denotes single statistic; raw json
    - time: timestamp of statistic
    - data: raw json statistic data
    """
    time = models.DateTimeField()
    data = models.TextField()

    def pretty(self):
        time_str = localtime(self.time).isoformat()
        return f'{time_str} {self.data}'

    def __str__(self):
        return f'{self.time}' class UserLog(models.Model):
    """
    denotes single log for an user or global event
    - user:  user object
    - level: level of log; python log level
    - time:  event time
    - ip:    event ip
    - hide:  hide log in user log page
    - text:  detail log message
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE,
                             related_name='user_logs', blank=True, null=True)
    level = models.IntegerField()
    time = models.DateTimeField(auto_now=True)
    ip = models.GenericIPAddressField()
    hide = models.BooleanField(default=False)
    text = models.CharField(max_length=500)

    def pretty(self):
        time = localtime(self.time).isoformat()
        username = self.user.username if self.user else 'unknown'
        level, ip, text = self.level, self.ip, self.text
        return f'{time}/{level} ({ip}, {username}) {text}'

    def __str__(self):
        time_str = localtime(self.time).isoformat()
        return f'{time_str}/{self.level} ({self.user}) {self.text}' class UserProfile(models.Model):
    """
    denotes additional information of single user
    - user:            user object
    - gender:          gender; *H / *M / *F / *E or others
    - birthday:        birthday
    - point:           point for public services
    - point_test:      point for test services
    - email_new:       new email before auth
    - email_authed:    email authed state
    - test_only:       indicate test only account
    - test_enabled:    test mode state
    - facebook_id:     facebook unique id
    - twitter_id:      twitter unique id
    - kaist_id:        kaist uid
    - kaist_info:      additional kaist info
    - kaist_info_time: kaist info updated time
    - sparcs_id:       sparcs id iff sparcs member
    - expire_time:     expire time for permanent deletion
    """
    user = models.OneToOneField(User, on_delete=models.CASCADE,
                                related_name='profile')
    gender = models.CharField(max_length=30)
    birthday = models.DateField(blank=True, null=True)
    point = models.IntegerField(default=0)
    point_test = models.IntegerField(default=0)
    email_new = models.EmailField(blank=True, null=True)
    email_authed = models.BooleanField(default=False)
    test_only = models.BooleanField(default=False)
    test_enabled = models.BooleanField(default=False)
    facebook_id = models.CharField(max_length=50, blank=True, null=True)
    twitter_id = models.CharField(max_length=50, blank=True, null=True)
    kaist_id = models.CharField(max_length=50, blank=True, null=True)
    kaist_info = models.TextField(blank=True, null=True)
    kaist_info_time = models.DateField(blank=True, null=True)
    sparcs_id = models.CharField(max_length=50, blank=True, null=True)
    expire_time = models.DateTimeField(blank=True, null=True)

    @property
    def flags(self):
        return {
            'test': self.test_enabled,
            'test-only': self.test_only,
            'dev': self.user.is_staff or bool(self.sparcs_id),
            'sparcs': bool(self.sparcs_id),
            'sysop': self.user.is_staff,
        }

    def gender_display(self):
        if self.gender == '*M':
            return 'Male'
        elif self.gender == '*F':
            return 'Female'
        elif self.gender == '*H':
            return 'Hide'
        elif self.gender == '*E':
            return 'etc'
        return self.gender

    def activate(self):
        if self.expire_time:
            self.expire_time = None
            self.save()
            return True
        return False

    def save_kaist_info(self, info):
        self.kaist_id = info['userid']
        self.kaist_info = json.dumps(info['kaist_info'])
        self.kaist_info_time = timezone.now()
        self.save()

    def __str__(self):
        return f'{self.user}''s profile'
from django.contrib import admin
from django.contrib.auth import admin as uadmin
from django.contrib.auth.models import User
from apps.core.models import (
    AccessToken, Document, EmailAuthToken, EmailDomain, InquiryMail, Notice,
    PointLog, ResetPWToken, Service, ServiceMap,
    Statistic, UserLog, UserProfile,
)



admin.site.unregister(User)


# Filters
class UserFilter(admin.SimpleListFilter):
    title = 'user'
    parameter_name = 'user'

    def lookups(self, request, model_admin):
        users = [u.user for u in model_admin.model.objects.all()]
        users = list(set(users))
        return [(u.id, u.last_name + ' ' + u.first_name) for u in users]

    def queryset(self, request, qs):
        if self.value():
            return qs.filter(user__id__exact=self.value())
        else:
            return qs


# Admin for General Objects
@admin.register(Notice)
class NoticeAdmin(admin.ModelAdmin):
    list_display = ('title', 'valid_from', 'valid_to', 'text')


@admin.register(Statistic)
class StatisticAdmin(admin.ModelAdmin):
    list_display = ('time', 'data')
    readonly_fields = list(map(lambda x: x.name, Statistic._meta.fields))

    def has_add_permission(self, request, obj=None):
        return False


@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
    list_display = ('category', 'version', 'date_apply', 'date_version')


# Admin for Service Related Objects
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
    list_display = ('name', 'is_shown', 'alias', 'scope',
                    'admin_user', 'main_url', 'login_callback_url',
                    'unregister_url', 'cooltime')


@admin.register(ServiceMap)
class ServiceMapAdmin(admin.ModelAdmin):
    actions = None
    list_display = ('sid', 'user', 'service',
                    'register_time', 'unregister_time')
    readonly_fields = ('user', 'service', 'sid')
    search_fields = ('user__email', 'service__name', 'service__alias')

    def has_add_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


@admin.register(AccessToken)
class AccessTokenAdmin(admin.ModelAdmin):
    list_display = ('tokenid', 'user', 'service', 'expire_time')
    list_filter = (UserFilter, )
    readonly_fields = list(map(lambda x: x.name, AccessToken._meta.fields))

    def has_add_permission(self, request, obj=None):
        return False


# Admin for User Related Objects
@admin.register(User)
class UserAdmin(uadmin.UserAdmin):
    class UserProfileInline(admin.StackedInline):
        model = UserProfile
        can_delete = False
        fieldsets = (
            ('General', {
                'fields': ('gender', 'birthday',
                           'email_authed', 'expire_time'),
            }),
            ('Points', {
                'fields': ('point', 'point_test'),
            }),
            ('SNS', {
                'fields': ('facebook_id', 'twitter_id',
                           'kaist_id', 'kaist_info_time', 'kaist_info'),
            }),
            ('Dev / Debug', {
                'fields': ('sparcs_id', 'test_only', 'test_enabled'),
            }),
        )

    def get_profile(self, obj):

        return UserProfile.objects.get(user=obj)