"""Contains base (abstract) classes used in :mod:`.models`
"""

import re

import django.core.exceptions
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models

from .validators import CWRFieldValidator
from .societies import SOCIETIES
from uuid import uuid4
import base64


def upload_to(instance, filename):
    ext = filename.rsplit('.', 1)[-1]
    folder = instance._meta.model_name
    fn = base64.urlsafe_b64encode(uuid4().bytes).rstrip(b'=')
    return f'{folder}/{fn}.{ext}'


class NotesManager(models.Manager):
    """Manager for objects inheriting from :class:`NotesBase`.

    Defers :attr:`NotesBase.notes` field.
    """
    def get_queryset(self):
        """Defer :attr:`NotesBase.notes` field."""
        qs = super().get_queryset()
        qs = qs.defer('notes')
        return qs


class NotesBase(models.Model):
    """Abstract class for all classes that have notes.

    Attributes:
        notes (django.db.models.TextField): Notes, free internal text field
    """

    class Meta:
        abstract = True

    objects = NotesManager()

    notes = models.TextField(blank=True)


class DescriptionBase(models.Model):
    """Abstract class for all classes that have publicly visible descriptions.

    Attributes:
        description (django.db.models.TextField): Public description
    """

    class Meta:
        abstract = True

    description = models.TextField(blank=True)


class TitleBase(models.Model):
    """Abstract class for all classes that have a title.

    Attributes:
        title (django.db.models.CharField): Title, used in work title,
            alternate title, etc.
    """

    class Meta:
        abstract = True

    title = models.CharField(
        max_length=60, db_index=True,
        validators=(CWRFieldValidator('title'),))

    def __str__(self):
        return self.title


class PersonBase(models.Model):
    """Base class for all classes that contain people with first and last name.

    This includes writers and artists. For bands, only the last name field is
    used.

    Attributes:
        first_name (django.db.models.CharField): First Name
        last_name (django.db.models.CharField): Last Name
    """

    class Meta:
        abstract = True

    first_name = models.CharField(
        max_length=30, blank=True,
        validators=(CWRFieldValidator('name'),))
    last_name = models.CharField(
        max_length=45, db_index=True,
        validators=(CWRFieldValidator('name'),))
    image = models.ImageField(max_length=255, upload_to=upload_to, blank=True)

    def __str__(self):
        if self.first_name:
            return '{0.first_name} {0.last_name}'.format(self).upper()
        return self.last_name.upper()


class SocietyAffiliationBase(models.Model):
    """Abstract base for all objects with CMO affiliations

    Attributes:
        pr_society (django.db.models.CharField):
            Performing Rights Society Code
        mr_society (django.db.models.CharField):
            Mechanical Rights Society Code
        sr_society (django.db.models.CharField):
            Sync. Rights Society Code
    """

    class Meta:
        abstract = True

    pr_society = models.CharField(
        'Performance rights society', max_length=3, blank=True, null=True,
        choices=SOCIETIES + [('99', 'NO SOCIETY')])
    mr_society = models.CharField(
        'Mechanical rights society', max_length=3, blank=True, null=True,
        choices=SOCIETIES)
    sr_society = models.CharField(
        'Synchronization rights society', max_length=3, blank=True, null=True,
        choices=SOCIETIES)


class IPIBase(models.Model):
    """Abstract base for all objects containing IPI numbers.

    Attributes:
        ipi_base (django.db.models.CharField): IPI Base Number
        ipi_name (django.db.models.CharField): IPI Name Number
        _can_be_controlled (django.db.models.BooleanField):
            used to determine if there is enough data for a writer
            to be controlled.
    """

    class Meta:
        abstract = True

    ipi_name = models.CharField(
        'IPI name #', max_length=11, blank=True, null=True, unique=True,
        validators=(CWRFieldValidator('ipi_name'),))
    ipi_base = models.CharField(
        'IPI base #', max_length=15, blank=True, null=True,
        validators=(CWRFieldValidator('ipi_base'),))

    _can_be_controlled = models.BooleanField(
        verbose_name='Can be controlled', editable=False, default=False)

    def clean_fields(self, *args, **kwargs):
        """
        Data cleanup, allowing various import formats to be converted into
        consistently formatted data.
        """
        if self.ipi_name:
            self.ipi_name = self.ipi_name.zfill(11)
        if self.ipi_base:
            self.ipi_base = self.ipi_base.replace('.', '').upper()
            self.ipi_base = re.sub(
                r'(I).?(\d{9}).?(\d)',
                r'\1-\2-\3',
                self.ipi_base)
        return super().clean_fields(*args, **kwargs)


class IPIWithGeneralAgreementBase(IPIBase, SocietyAffiliationBase):
    """Abstract base for all objects with general agreements.

    Attributes:
        saan (django.db.models.CharField):
            Society-assigned agreement number, in this context it is used for
            general agreements, for specific agreements use
            :attr:`.models.WriterInWork.saan`.
        generally_controlled (django.db.models.BooleanField):
            flags if a writer is generally controlled (in all works)
        publisher_fee (django.db.models.DecimalField):
            this field is used in calculating publishing fees
    """

    class Meta:
        abstract = True

    saan = models.CharField(
        'SAAN',
        help_text='Use this field for a general original publishing '
            'agreement.',
        validators=(CWRFieldValidator('saan'),),
        max_length=14, blank=True, null=True)

    generally_controlled = models.BooleanField(
        'General agreement', default=False)
    publisher_fee = models.DecimalField(
        max_digits=5, decimal_places=2, blank=True, null=True,
        validators=[MinValueValidator(0), MaxValueValidator(100)])

    def clean(self):
        """Clean the data and validate."""

        self._can_be_controlled = (
                bool(self.ipi_name) & bool(self.pr_society))
        if self.ipi_name == '00000000000':
            self.ipi_name = None
        if self.pr_society == '99':
            self.pr_society = None

        d = {}
        if not self.generally_controlled:
            if self.saan:
                d['saan'] = 'Only for a general agreement.'
            if self.publisher_fee:
                d['publisher_fee'] = 'Only for a general agreement.'
        else:
            if not self._can_be_controlled:
                d['generally_controlled'] = (
                    'IPI name number and PR society fields are required for '
                    'a controlled writer. See "Writers" in the user manual.')
        if d:
            raise django.core.exceptions.ValidationError(d)

    def clean_fields(self, *args, **kwargs):
        """
        Data cleanup, allowing various import formats to be converted into
        consistently formatted data.
        """
        if self.saan:
            self.saan = self.saan.upper()  # only in CWR, uppercase anyway
        super().clean_fields(*args, **kwargs)


class ArtistBase(PersonBase, NotesBase, DescriptionBase):
    """Performing artist base class.

    Attributes:
        isni (django.db.models.CharField): International Standard Name Id
    """

    class Meta:
        verbose_name = 'Performing Artist'
        verbose_name_plural = 'Performing Artists'
        ordering = ('last_name', 'first_name', '-id')
        abstract = True

    isni = models.CharField(
        'ISNI',
        max_length=16, blank=True, null=True, unique=True,
        validators=(CWRFieldValidator('isni'),))

    def clean_fields(self, *args, **kwargs):
        """ISNI cleanup"""
        if self.isni:
            self.isni = self.isni.rjust(16, '0').upper()
        return models.Model.clean_fields(self, *args, **kwargs)


class WriterBase(
        PersonBase, IPIWithGeneralAgreementBase, NotesBase, DescriptionBase):
    """Base class for writers.
    """

    class Meta:
        ordering = ('last_name', 'first_name', 'ipi_name', '-id')
        verbose_name_plural = 'Writers'
        abstract = True


class LabelBase(NotesBase, DescriptionBase):
    """Music Label base class.

    Attributes:
        name (django.db.models.CharField): Label Name
    """

    class Meta:
        verbose_name_plural = 'Music Labels'
        ordering = ('name',)
        abstract = True

    name = models.CharField(
        max_length=60, unique=True,
        validators=(CWRFieldValidator('name'),))
    image = models.ImageField(
        'Logo', max_length=255, upload_to=upload_to, blank=True)


class LibraryBase(models.Model):
    """Music Library base class.

    Attributes:
        name (django.db.models.CharField): Library Name
    """

    class Meta:
        verbose_name_plural = 'Music Libraries'
        ordering = ('name',)
        abstract = True

    name = models.CharField(
        max_length=60, unique=True,
        validators=(CWRFieldValidator('name'),))


class ReleaseBase(DescriptionBase):
    """Music Release base class

    Attributes:
        cd_identifier (django.db.models.CharField): CD Identifier, used when \
        origin is library
        library (django.db.models.CharField): Library Name
        release_date (django.db.models.DateField): Date of the release
        ean (django.db.models.CharField): EAN code
        release_label (django.db.models.CharField): Label Name
        release_title (django.db.models.CharField): Title of the release
    """

    class Meta:
        ordering = ('release_title', 'cd_identifier', '-id')
        abstract = True

    cd_identifier = models.CharField(
        'CD identifier',
        max_length=15, blank=True, null=True, unique=True,
        validators=(CWRFieldValidator('name'),))
    release_date = models.DateField(
        blank=True, null=True)
    release_title = models.CharField(
        'Release (album) title ',
        max_length=60, blank=True, null=True,
        validators=(CWRFieldValidator('title'),))
    ean = models.CharField(
        'Release (album) EAN',
        max_length=13, blank=True, null=True, unique=True,
        validators=(CWRFieldValidator('ean'),))
    image = models.ImageField(
        'Cover Art', max_length=255, upload_to=upload_to, blank=True)
