"""Models for the mainapp."""

from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError


# region Anglers & Groups

class Angler(models.Model):
    """A participant of one or more events.

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): Full name or label describing the angler.
        redact (bool): Determines if the `name` attr should be redacted.
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    redact = models.BooleanField()

    class Meta:
        verbose_name = "angler"
        verbose_name_plural = "anglers"

    def __str__(self):
        return self.name


class AnglerGroup(models.Model):
    """A group of one or more anglers.

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): A human-readable label to identify this group.
        anglers (list of `Angler`): The members of this group.
        type (int): An enum-like value representing the type of group.
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    anglers = models.ForeignKey(to=Angler, on_delete=models.CASCADE)

    TYPES = (
        (0, "team type 1"),  # TODO:  Change these placeholders for actual values.
        (1, "team type 2"),
        (2, "team type 3")
    )

    type = models.PositiveSmallIntegerField(choices=TYPES)

    class Meta:
        verbose_name = "angler group"
        verbose_name_plural = "angler groups"

    def __str__(self):
        return f"{self.name} ({self.anglers.count} anglers)"


# region Venues & Waters


class VenueAddress(models.Model):
    id = models.AutoField(primary_key=True)
    street_number = models.IntegerField()
    street_address = models.CharField(max_length=256)
    town = models.CharField(max_length=256)
    provence = models.CharField(max_length=256)
    post_code = models.CharField(max_length=32)
    satnav_post_code = models.CharField(max_length=32)
    country = models.CharField(max_length=128)
    latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
    longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)

    class Meta:
        verbose_name = "venue address"
        verbose_name_plural = "venue addresses"

    def __str__(self):
        return f"{self.street_address}, {self.town} ({self.country})"


class VenueContacts(models.Model):
    id = models.AutoField(primary_key=True)
    phone_number = models.CharField(max_length=64)
    email_address = models.EmailField()
    website_url = models.URLField()
    facebook_url = models.URLField()
    twitter_url = models.URLField()
    instagram_url = models.URLField()

    class Meta:
        verbose_name = "venue contacts"
        verbose_name_plural = "venue contacts"

    def __str__(self):
        return self.email_address


class Venue(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    description = models.CharField(max_length=384)
    extra_notes = models.CharField(max_length=1028)

    created_at = models.DateTimeField(default=timezone.now, editable=False)
    updated_at = models.DateTimeField(default=timezone.now, editable=False)

    profile_picture = models.ImageField()
    banner_picture = models.ImageField()

    class Types:
        FISHERY = 0
        CLUB = 1
        PRIVATE = 2

    type = models.PositiveSmallIntegerField(
        choices=(
            (Types.FISHERY, "Fishery"),
            (Types.CLUB, "Club"),
            (Types.PRIVATE, "Private")
        )
    )

    address = models.ForeignKey(to=VenueAddress, on_delete=models.CASCADE)
    contacts = models.ForeignKey(to=VenueContacts, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "venue"
        verbose_name_plural = "venues"

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.updated_at = timezone.now()
        return super().save(*args, **kwargs)

    @property
    def waters(self):
        """Returns all waters belonging to this venue."""
        return Waters.objects.filter(venue=self)


class Waters(models.Model):
    """A body of water belonging to a particular venue.

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): A human-readable label to identify this item.
        pegs_from (int): The lowest peg number available.
        pegs_to (int): The highest peg number available.
        map (file): An image map showing the body of water.
        venue (`Venue`): The real world venue where this waters can be found.
        type (int): The type of waters represented (canal, river, etc...)
        fish_type (int): The fish categories that can be found in this water.
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    pegs_from = models.IntegerField()
    pegs_to = models.IntegerField()
    map = models.ImageField()
    venue = models.ForeignKey(to=Venue, on_delete=models.CASCADE)

    class Types:
        COMMERCIAL = 0
        NATURAL_STILL = 1
        CANAL = 2
        RIVER = 3
        LOCH = 4

    type = models.PositiveSmallIntegerField(
        choices=(
            (Types.COMMERCIAL, "Commercial Water"),
            (Types.NATURAL_STILL, "Natural Still Water"),
            (Types.CANAL, "Canal"),
            (Types.RIVER, "River"),
            (Types.LOCH, "Loch")
        )
    )

    class FishTypes:
        COARSE = 0
        SPECIMEN_CARP = 1
        GAME = 2
        PREDATOR = 3

    fish_types = models.PositiveSmallIntegerField(
        choices=(
            (FishTypes.COARSE, "Coarse"),
            (FishTypes.SPECIMEN_CARP, "Specimen Carp"),
            (FishTypes.GAME, "Game"),
            (FishTypes.PREDATOR, "Predator")
        )
    )

    class Meta:
        verbose_name = "waters"
        verbose_name_plural = "waters"

    def __str__(self):
        return self.name


# region Leagues & Matches


class Match(models.Model):
    """Represents an a fishing event/competition.

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): A human-readable label to identify this item.
        description (str): A detailed description of the event.
        venue (`Venue`): Where this match is hosted.
        waters (`Waters`): Which waters will be used in this match, can only use waters from the assigned venue.
        meeting_point (str): TODO: what is this for?
        use_metric (bool): Should the metric system be used in measurements, imperial is used if `False`.
        allow_in_tournaments (bool): Should this match be allowed in tournaments? (TODO: is this correct?)
        start_datetime (datetime): When the match will begin.
        end_datetime (datetime): When the match will finish.
        draw_datetime (datetime): When the draw is made (during the match).
        type (int): TODO: match types?
        competitor_type (int): TODO what are these?
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    description = models.CharField(max_length=1024)

    venue = models.ForeignKey(to=Venue, on_delete=models.CASCADE)
    waters = models.ForeignKey(to=Waters, on_delete=models.CASCADE)  # can only select waters from the matching venue

    meeting_point = models.CharField(max_length=1024)
    use_metric = models.BooleanField()
    allow_in_tournaments = models.BooleanField()

    start_datetime = models.DateTimeField()
    end_datetime = models.DateTimeField()
    draw_datetime = models.DateTimeField()

    TYPES = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    type = models.PositiveSmallIntegerField(choices=TYPES)

    COMPETITOR_TYPES = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    competitor_type = models.PositiveSmallIntegerField(choices=COMPETITOR_TYPES)

    class Meta:
        verbose_name = "match"
        verbose_name_plural = "matches"

    def __str__(self):
        return self.name


class LeagueRule(models.Model):
    """Rules for leagues.

    Rules can be reused against multiple leagues, and aren't strictly owned by
    any particular `League` instance.

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): A human-readable label to identify this item.
        ranking_system (int): TODO
        points_allocation (int): TODO
        points_awarded (int): TODO
        place_secondly_by_weight (bool): TODO
        team_places_secondly_by_section (bool): TODO
        section_placed_positions = (int): TODO
        worst_place_limits (bool): TODO
        attendance_points (int): TODO
        did_not_weigh (int): TODO
        did_not_weigh_value (int): TODO
        left_early (int): TODO
        left_early_value (int): TODO
        did_not_book (int): TODO
        did_not_book_value (int): TODO
        disqualification (int): TODO
        disqualification_value (int): TODO
        best_league_sessions (int): TODO
        worst_league_sessions (int): TODO
        match_placed_positions (int): TODO
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)

    RANKING_SYSTEMS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    ranking_system = models.PositiveSmallIntegerField(choices=RANKING_SYSTEMS)

    POINTS_ALLOCATIONS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    points_allocation = models.PositiveSmallIntegerField(choices=POINTS_ALLOCATIONS)

    POINTS_AWARDED = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    points_awarded = models.PositiveSmallIntegerField(choices=POINTS_AWARDED)

    place_secondly_by_weight = models.BooleanField()
    team_places_secondly_by_section = models.BooleanField()
    section_placed_positions = models.IntegerField()
    worst_place_limits = models.BooleanField()
    attendance_points = models.IntegerField()

    DID_NOT_WEIGHS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    did_not_weigh = models.PositiveSmallIntegerField(choices=DID_NOT_WEIGHS)
    did_not_weigh_value = models.IntegerField()

    LEFT_EARLYS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    left_early = models.PositiveSmallIntegerField(choices=LEFT_EARLYS)
    left_early_value = models.IntegerField()

    DID_NOT_BOOKS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    did_not_book = models.PositiveSmallIntegerField(choices=DID_NOT_BOOKS)
    did_not_book = models.IntegerField()
 
    DISQUALIFICATIONS = (
        (0, "choice 1"),
        (1, "choice 2"),
        (2, "choice 3")
    )
    disqualification = models.PositiveSmallIntegerField(choices=DISQUALIFICATIONS)
    disqualification_value = models.IntegerField()

    best_league_sessions = models.IntegerField()
    worst_league_sessions = models.IntegerField()
    match_placed_positions = models.IntegerField()

    class Meta:
        verbose_name = "league rule"
        verbose_name_plural = "league rules"

    def __str__(self):
        return self.name


class League(models.Model):
    """A League of matches (TODO: should match not be foreignkey linked to league?)

    Attributes:
        id (int): Automatically incrementing identifier number.
        name (str): A human-readable label to identify this item.
        description (str):
        extra_notes (str):
        profile_picture (file):
        banner_picture (file):
        matches (list of `Match`):
        anglers (list of `Angler`):
        rules (list of `LeagueRule`):
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    description = models.CharField(max_length=384)
    extra_notes = models.CharField(max_length=1028)

    profile_picture = models.ImageField()
    banner_picture = models.ImageField()

    matches = models.ManyToManyField(to=Match)
    anglers = models.ManyToManyField(to=Angler)
    rules = models.ManyToManyField(to=LeagueRule)

    class Meta:
        verbose_name = "league"
        verbose_name_plural = "leagues"

    def __str__(self):
        return self.name

    @property
    def results(self):
        return LeagueResult.objects.filter(league=self)


class Sponsor(models.Model):
    """Represent those sponsoring.

    Attribute:
        id (int): Automatically incrementing identifier number.
        name (str): The sponsor's name.
        url (str): Link to the sponsor's external resource.
        image (file): An image representing the sponsor.
    """

    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    url = models.URLField()
    image = models.ImageField()

    class Meta:
        verbose_name = "sponsor"
        verbose_name_plural = "sponsors"

    def __str__(self):
        return self.name


class LeagueResult(models.Model):
    """An entry representing an angler's result in a league.

    Attributes:
        TODO
    """
    id = models.AutoField(primary_key=True)

    league = models.ForeignKey(to=League, on_delete=models.CASCADE)
    angler = models.ForeignKey(to=Angler, on_delete=models.CASCADE)
    sponsor = models.ForeignKey(to=Sponsor, on_delete=models.CASCADE, null=True, blank=True)

    total_weight = models.CharField(max_length=64)
    matches = models.IntegerField()
    date = models.DateField(default=timezone.now)

    class Meta:
        verbose_name = "league result"
        verbose_name_plural = "league results"

    def __str__(self):
        return f"{self.league.name} - {self.angler.name}"


# class Venue(models.Model):
#     """Represents a Venue and Waters."""

#     VENUE_TYPES = (
#         ("FISHERY", "Fishery"),
#         ("CLUB", "Club"),
#         ("PRIVATE", "Private")
#     )

#     name = models.CharField(max_length=255, null=True, blank=True)
#     description = models.TextField(blank=True, max_length=500, null=True,)
#     extra_notes = models.TextField(blank=True, null=True,)
#     venue_type = models.CharField(choices=VENUE_TYPES, max_length=50, null=True, blank=True)

#     # Contact information
#     phone_number = models.CharField(max_length=100, null=True, blank=True)
#     email_address = models.EmailField(null=True, blank=True)
#     website_url = models.URLField(null=True, blank=True)

#     # Location information
#     street_address = models.CharField(max_length=100, null=True, blank=True)
#     city = models.CharField(max_length=255, null=True, blank=True)
#     provence = models.CharField(max_length=100, null=True, blank=True)
#     postal_code = models.CharField(max_length=20, null=True, blank=True)
#     country = models.CharField(max_length=100, null=True, blank=True)
#     latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
#     longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)

#     # Socials information
#     twitter_url = models.URLField(blank=True, null=True)
#     instagram_url = models.URLField(blank=True, null=True)
#     facebook_url = models.URLField(blank=True, null=True)

#     active = models.BooleanField(default=True)

#     def __str__(self):
#         return self.name

#     def waters(self):
#         """Returns all waters linked to this venue."""

#         waters = Waters.objects.filter(venue=self)
#         return waters


# class Waters(models.Model):
#     """Represents the waters of a Venue"""

#     WATER_TYPES = (
#         ("CW", "Commercial Water"),
#         ("NSW", "Natural Still Water"),
#         ("C", "Canal"),
#         ("R", "River"),
#         ("L", "Loch"),
#     )

#     FISH_TYPES = (
#         ("C", "Coarse"),
#         ("SC", "Specimen Carp"),
#         ("G", "Game"),
#         ("P", "Predator"),
#     )

#     venue = models.ForeignKey(Venue, on_delete=models.CASCADE)

#     name = models.CharField(max_length=100)
#     description = models.TextField(max_length=255)

#     pegs_min = models.IntegerField()
#     pegs_max = models.IntegerField()
#     water_type = models.CharField(choices=WATER_TYPES, max_length=50)
#     fish_type = models.CharField(choices=FISH_TYPES, max_length=50)

#     # water_map = models.ImageField()

#     def __str__(self):
#         return self.name






# class ReusableAutoField(models.PositiveIntegerField):
#     """A django auto field that can reuse deleted primary keys."""

#     def get_next_available_id(self, model_cls, using=None):
#         """
#         Returns the next available id for the given model class.
#         """
#         all_ids = set(range(1, model_cls._default_manager.count()+1))
#         used_ids = set(model_cls._default_manager.all().values_list('pk', flat=True))
#         available_ids = all_ids - used_ids

#         if available_ids:
#             return min(available_ids)

#         if used_ids:
#             return max(used_ids) + 1

#         return 1

#     def get_default(self):
#         """Returns the default value for this field"""

#         return self.get_next_available_id(self.model)


# class SectionValidator:
#     """Validation class for the `section` field on the `member` model."""

#     def __init__(self, max_value="ZZZ"):
#         self.max_value = max_value.upper()
#         self.alphabet_size = ord("Z") - ord("A") + 1

#     def is_valid(self, section: str, team_sections: list[str]=None) -> bool:
#         """Returns boolean if the section passed is valid."""
#         section = section.upper()

#         if not self._is_alphanumeric(section):
#             return False

#         if not self._is_in_alphabet(section[0]):
#             return False

#         if not self._is_in_range(section):
#             return False

#         if team_sections:
#             if not self._is_unique(section, team_sections):
#                 return False

#             if not self._is_not_adjacent(section, team_sections):
#                 return False

#         return True

#     def _is_alphanumeric(self, section: str) -> bool:
#         """Returns boolean if all characters in the passed string are alphanumerical."""
#         return all(c.isalnum() for c in section)

#     def _is_in_alphabet(self, c) -> bool:
#         """Returns boolean if the passed character is alphabetical."""
#         return "A" <= c <= "Z"

#     def _is_in_range(self, section) -> bool:
#         """Returns boolean if the passed section less or equal to the max value."""
#         section_value = self._section_value(section)
#         max_value = self._section_value(self.max_value)

#         return section_value <= max_value

#     def _is_unique(self, section, team_sections) -> bool:
#         """Returns boolean if the passed section is unique amongst `team_sections`."""
#         return section not in team_sections

#     def _is_not_adjacent(self, section, team_sections) -> bool:
#         """Returns boolean if the passed section is not adjacent to any `team_sections`."""
#         for team_section in team_sections:
#             team_section_value = self._section_value(team_section)
#             section_value = self._section_value(section)
#             if abs(team_section_value - section_value) <= 1:
#                 return False

#         return True

#     def _section_value(self, section):
#         """Returns the value of the passed section."""
#         n = len(section)
#         value = sum((ord(c) - ord("A") + 1) * self.alphabet_size ** (n - i - 1) for i, c in enumerate(section))
#         return value


# class SectionManager(models.Manager):

#     @staticmethod
#     def get_max_section():
#         max_section = None
#         max_number = -1

#         # Iterate through all sections in the database
#         for section in Section.objects.all():
#             section_name = section.name
#             section_number = 0

#             # Calculate the section number based on the section name
#             for i, char in enumerate(section_name):
#                 section_number += (ord(char) - ord('A') + 1) * (26 ** (len(section_name) - i - 1))

#             # Check if this section has a higher number than the current maximum
#             if section_number > max_number:
#                 max_number = section_number
#                 max_section = section_name

#         return max_section

#     @staticmethod
#     def find_next_section(current_section):
#         if not current_section:
#             return 'A'

#         # Split current section name into a list of characters
#         chars = list(current_section)

#         # Increment the last character
#         chars[-1] = chr(ord(chars[-1]) + 1)

#         # Check if the last character is "Z", and carry over to the next character if necessary
#         for i in range(len(chars) - 1, -1, -1):
#             if chars[i] > 'Z':
#                 chars[i] = 'A'
#                 if i == 0:
#                     # If the first character needs to be incremented, add a new character "A"
#                     chars.insert(0, 'A')
#                 else:
#                     # Increment the previous character
#                     chars[i - 1] = chr(ord(chars[i - 1]) + 1)
#             else:
#                 break

#         # Join the characters back into a string and return the result
#         return ''.join(chars)

# class Section(models.Model):
#     """Represents a fishing area. Members can be assigned to a section,
#     but no 2 teammates can be in the same or adjacent section."""

#     character = models.CharField(max_length=3, unique=True, null=False)

#     objects = SectionManager()

#     def clean(self):
#         super().clean()
#         self.character = self.character.upper()

#     def __str__(self) -> str:
#         return self.character


# class Member(models.Model):
#     """Represents a member of a team"""

#     first_name = models.CharField(max_length=255)
#     last_name = models.CharField(max_length=255)
#     team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, related_name='members')
#     peg_number = models.PositiveIntegerField(null=True, editable=True, unique=True)
#     section = models.ForeignKey(to=Section, on_delete=models.SET_NULL, null=True, swappable=True, related_name='members')

#     def __init__(self, *args, **kwargs):
#         super().__init__(*args, **kwargs)

#         # If the peg_number field is not set, we assign it the smallest
#         # available positive integer, excluding any used values.
#         if not self.peg_number:
#             used_peg_numbers = Member.objects.exclude(id=self.id).exclude(peg_number=None).values_list("peg_number", flat=True)
#             peg_numbers = set(range(1, Member.objects.count() + 1)) - set(used_peg_numbers)
#             if peg_numbers:
#                 self.peg_number = min (peg_numbers)

#     def __str__(self):
#         return f"{self.first_name} {self.last_name} (team {self.team.number}) [section {self.section.character}]"

#     @property
#     def fullname(self) -> str:
#         return f"{self.first_name} {self.last_name}"


# class Team(models.Model):
#     """Represents a team"""

#     number = models.PositiveIntegerField(unique=True, null=False, blank=False)

#     def __str__(self):
#         return f"Team {self.number}"
