# -*- coding: utf-8 -*-
"""
MarkdownxFields can handle what?

Special links: {LA-LinearAlgebra <optional link text> <#optional anchor>}

"""

import datetime
import os
from zoneinfo import ZoneInfo
import urllib.parse
import re
from pathlib import Path

from django.db import models
from django.template.loader import get_template
#from djrichtextfield.models import RichTextField
from markdownx.models import MarkdownxField # new
import markdown2 # new

from django.utils.safestring import mark_safe
from django.core.files.storage import FileSystemStorage
from django.conf import settings

from djphys.models import Course, FileKind, CourseFile
from djphys.funcs import get_epoch
#from djphys.constants import PERMISSION_CHOICES
from people.models import CourseRecord

# new
markdowner = markdown2.Markdown(
    extras=['code-friendly', 'fenced-code-blocks', 'numbering', 'tables']
)


def make_long_regex():
    segments = [
        r'<a href="https?://saeta.physics.hmc.edu/', # no group
        r'([a-z]\d\d\d)/',  # group 1 = order_field
        r'([A-Z]{2}-.*?)\.html',  # group 2 = page name
        r'(|#[^ ]*)',  # group 3 is empty or anchor
        r'"(| target="[^"]*")',  # group 4 is target
        r'>([^<]*)</a>', # group 5 is title text
    ]
    return segments


REGEX_SHORT = r'\{([A-Z]{2}-[-A-Za-z0-9]+) ?([^#}]*) ?(|#[^ }]*)\}'
REGEX_LONG = "".join(make_long_regex())


def convert_shorturls(t: str, order_field: str) -> str:
    """
    Replace short urls of the form {FO-Series ...} to valid html.
    """

    def makelong(m) -> str:
        page, title, anchor = m.groups()
        pageurl = f"https://saeta.physics.hmc.edu/{order_field}/{page}.html"
        if not title:
            title = decamel(page[1 + page.index("-"):])
        url = f'<a href="{pageurl}{anchor}" target="_pages">{title}</a>'
        return url

    txt = re.sub(REGEX_SHORT, makelong, t, flags=re.M)
    return mark_safe(txt)


def convert_longurls(t: str) -> str:
    """
    Substitute short urls for long ones.
    """
    def makeshort(m) -> str:
        order_field, page, anchor, target, title = m.groups()
        guts = f"{page}"
        if title:
            guts += f" {title}"
        if anchor:
            guts += f" {anchor}"
        return "{%s}" % guts

    txt = re.sub(REGEX_LONG, makeshort, t, flags=re.M | re.DOTALL)
    return txt


def md(s: str, order_field: str) -> str:
    t = markdowner.convert(s)
    t = re.sub(
        r'<pre>(.*?)</pre>',
        # r'<span class="latex"><code>\1</code></span>',
        r'<pre class="latex">\1</pre>',
        t,
        flags=re.M + re.DOTALL,
    )

    return convert_shorturls(t, order_field)


private_fs = FileSystemStorage(
    location=settings.PRIVATE_ROOT, base_url=settings.PRIVATE_URL)


# A CourseSite should reference a course. On the extreme off chance
# that this becomes useful, however, it might be nice to be able to
# guest host. In such a case, we need a record to supply the necessary
# information we will be gleaning from a djphys.models.Course object.

SESSION_TYPE = (
    ('l', 'Lecture'),
    ('r', 'Recitation'),
    ('b', 'Lab'),
    ('e', 'Exam'),
    ('h', 'Holiday'),
)

ACCESS_CHOICES = (
    ('C', 'Claremont only'),
    ('F', 'Physics faculty only'),
    ('1', 'Anyone'),
    ('L', 'Only registered users'),
)


def decamel(txt: str):
    "Split camel case into separate words"

    def cap(x):
        return ord('A') <= ord(x) <= ord('Z')

    t = txt[0]
    n = 1
    while n < len(txt):
        if cap(txt[n]) and not cap(txt[n - 1]):
            t += ' '
        t += txt[n]
        n += 1
    return t


def make_links(t: str, order_field: str):
    r"""
    Scan txt looking for link text to convert into actual hyperlinks.
    These are in two forms:
    1) https?://[^ "\']*
    2) \{[A-Z]{2}-[^ }]*\}   e.g., {FO-FourierSeries}

    Form the links and return the resulting string after marking it safe.
    """

    def makelink(m):
        protocol, address = m.groups()
        fields = address.split('/')
        linktext = fields[-1].split('.')[0]
        link = f'<a href="{protocol}{address}">{linktext}</a>'
        return link

    regex = r'(<!href=")(https?://)([^ "\']*)'
    txt = re.sub(regex, makelink, t, flags=re.M | re.I)
    return convert_shorturls(txt, order_field)

    #txt = []
    #pt = 0
    #for m in re.finditer(r'(https?://)([^ "\']*)', t, flags=re.M | re.I):
    #txt.append(t[pt:m.start()])
    #flds = m.group(2).split('/')
    #linktext = flds[-1].split('.')[0]
    #txt.append('<a href="' + m.group(0)+'">' + linktext + '</a>')
    #pt = m.end()
    #txt.append(t[pt:])

    ## Now handle links of the form {FO-FourierSeries}
    #s = "".join(txt)
    #txt, pt = [], 0
    #for m in re.finditer(r'\{([A-Z]{2}-[^ }]*)\}', s, flags=re.M):
    #txt.append(s[pt:m.start()])
    #linktext = m.group(1).split('.')[0]
    #url = f"https://saeta.physics.hmc.edu/{order_field}/{linktext}.html"
    #txt.append(f'<a href="{url}">{decamel(linktext[1 + linktext.index("-"):])}</a>')
    #pt = m.end()
    #txt.append(t[pt:])
    #return mark_safe("".join(txt))


class CourseSite(models.Model):
    course = models.ForeignKey(Course, null=True, on_delete=models.CASCADE)
    logo = models.ForeignKey(CourseFile, blank=True,
                             null=True, on_delete=models.CASCADE)
    style = models.TextField(null=True, blank=True,
                             help_text='Define any CSS styles here you wish used on the site')

    def __str__(self):
        return f"CourseSite for {self.course.name}"

    def course_record(self, epoch):
        crs = CourseRecord.objects.filter(course=self.course,
                                          year=epoch.year, semester=epoch.semester)
        if crs.count() == 2:
            crs = crs.filter(part=epoch.portion)
        if crs.count() == 1:
            return crs[0]
        return None

    def add_weeks(self, src):
        """Add a column for the week of the semester.
        The source text has a number of <tr> lines that include a field
        week="n". Each time that number changes, we need to include a
        narrow column that spans the appropriate number of rows to show the
        full week on the left. We need to extract what's inside the
        <tr> </tr>, so we can add a multicolumn td on the first row of a
        week.
        """
        pat = re.compile(r'(<tr class=.*?</tr>)', re.DOTALL)
        rows = pat.findall(src)
        weeks = dict()
        for r in rows:
            m = re.search(r'week="(\d*)"', r)
            week = int(m.group(1))
            r += "\n"
            if week not in weeks:
                weeks[week] = [r]
            else:
                weeks[week].append(r)

        # Install the multirow cell reporting the week
        for k, v in weeks.items():
            to_insert = f'<td rowspan="{len(
                v)}" style="width: 16px; padding: 0px; vertical-align: middle;"><div class="rot">Week&nbsp;{k}</div></td>\n'
            where = v[0].index('<td')
            v[0] = v[0][:where] + to_insert + v[0][where:]

        table_start = '<table class="calendar table">'
        n_table_start = src.index(table_start)
        n_table_end = src.index('</table>')

        chunks = [
            src[:n_table_start],
            table_start,
            "\n<tr>\n",
            '<td class="week">&nbsp;</td>\n',
            src[src.index("<th>"):src.index("</tr>")],
            "</tr>\n",
        ]
        # Now add the modified rows
        for k, v in weeks.items():
            for day in v:
                chunks.append(day)
        chunks.append(src[src.index('</table>'):])
        return "".join(chunks)

    def calendar(self, request):
        from people.funcs import is_faculty
        from courses.forms import LoadScheduleForm

        course_rec = self.course_record(get_epoch(request))
        if not course_rec:
            return
        meetings = Meeting.objects.filter(
            course_record=course_rec).order_by('date')
        temp = get_template('courses/meeting.html')
        meets = []
        counters = dict()
        editable = is_faculty(request.user)
        for m in meetings:
            st = m.session_type
            if st not in counters:
                counters[st] = 0
            counters[st] += 1
            meets.append(temp.render(
                {'meeting': m, 'number': counters[st], 'request': request,
                 'editable': editable, }))
        temp = get_template('courses/calendar.html')
        dic = dict(meetings=meets, course_record=course_rec)
        if is_faculty(request.user):
            dic['loadform'] = LoadScheduleForm()

        src = temp.render(dic)
        # Now, let's try to knit together weeks by installing a
        # week column each time the week changes
        with_weeks = self.add_weeks(src)
        #return mark_safe(src)
        return mark_safe(with_weeks)

    def make_calendar(self, course_record, lectures="", recitations="", labs=""):
        start, finish = course_record.span()  # two datetime.dates
        daymap = {0: 'M', 1: 'T', 2: 'W', 3: 'R', 4: 'F'}
        everything = lectures + recitations + labs
        for day in range(0, 5):
            dayletter = daymap[day]
            if dayletter in everything:
                dt = day - start.weekday()
                if dt < 0:
                    dt += 7
                d = start + datetime.timedelta(days=dt)
                while d <= finish:
                    stypes = []
                    if dayletter in lectures:
                        stypes.append('l')
                    if dayletter in recitations:
                        stypes.append('r')
                    if dayletter in labs:
                        stypes.append('b')
                    for st in stypes:
                        Meeting.objects.create(course_record=course_record,
                                               session_type=st, date=d)
                    d += datetime.timedelta(days=7)


class URL(models.Model):
    url = models.URLField()
    date_posted = models.DateTimeField(auto_now_add=True)
    description = models.CharField(max_length=255)
    release_time = models.DateTimeField(blank=True, null=True)
    release_until = models.DateTimeField(blank=True, null=True)

    def __str__(self):
        return self.url

    @property
    def script(self):
        """Write an entry in a script file to describe this url link"""
        long = f'<a href="{self.url}">{self.description}</a>'
        short = convert_longurls(long)
        return short

    @property
    def link(self):
        from django.conf import settings
        icon_url = f'<img src="{
            settings.STATIC_URL}ico/link.png" class="icon">'
        return mark_safe(
            f'<a href="{self.url}" target="_new">{
                icon_url}&nbsp;{self.description}</a>&nbsp;'
        )


class Meeting(models.Model):
    course_record = models.ForeignKey(CourseRecord, on_delete=models.CASCADE)
    session_type = models.CharField(max_length=1, choices=SESSION_TYPE)
    date = models.DateField()
    topic = models.CharField(max_length=100, blank=True, null=True)
    homework = models.CharField(max_length=100, blank=True, null=True)
    reading = models.CharField(max_length=100, blank=True, null=True)
    exam = models.CharField(max_length=100, blank=True, null=True)
    # notes = RichTextField(blank=True, null=True)
    notes = MarkdownxField(blank=True, null=True)
    hide = models.BooleanField(default=False)
    urls = models.ManyToManyField(URL, blank=True)

    @property
    def course(self):
        return self.course_record.course

    def __str__(self):
        s = "{0} {1} {2}".format(self.course.order_field,
                                 self.date, self.get_session_type_display())
        if self.topic:
            s += ": {0}".format(self.topic)
        if self.reading:
            s += ", Reading: {0}".format(self.reading)
        if self.homework:
            s += ", Homework due: {0}".format(self.homework)
        return s

    def format(self, f):
        return self.date.strftime(f)

    def DayName(self):
        return self.format("%A")

    def Day(self):
        return self.format("%a")

    def Month(self):
        return self.format("%B")

    def month(self):
        return self.format("%b")

    @property
    def iso(self):
        return self.format("%Y-%m-%d")

    def short_form(self):
        return "{0}/{1}".format(self.date.month, self.date.day)

    def fields(self):
        dic = {}
        for k in ('homework', 'reading', 'exam'):
            if getattr(self, k):
                dic[k] = convert_shorturls(
                    getattr(self, k), self.course.order_field)
        return dic

    @property
    def notes_md(self):
        return md(self.notes, self.course.order_field)

    @property
    def year(self):
        return self.date.year

    @property
    def week(self):
        """
        Attempt to determine the number of the week in the semester
        that this meeting's date occurs in.
        """
        start = self.course_record.epoch.start_
        day = self.date - start
        week = 1 + day.days // 7
        return week

    class Meta:
        ordering = ['-date', ]


def FileLocation(obj, filename):
    # We will flesh this out so that it looks at the kind of object and then figures
    # out where the right place to put the file is
    if isinstance(obj, MeetingFile):
        crec = obj.meeting.course_record
        cname = crec.course.order_field
        year = crec.year
        semester = crec.semester
        portion = crec.part
        # figure out how specialized to make the path
        if portion == 'a':
            portion = ''
        ysem = "{0}{1}{2}".format(year, semester, portion)
        folder = os.path.join("courses", cname, ysem)
    else:
        folder = "unknown"
    full_path = os.path.join(folder, filename)
    return full_path


class SecureCourseFile(models.Model):
    uploaded_by = models.ForeignKey(
        'auth.User', null=True, blank=True, on_delete=models.CASCADE)
    date_posted = models.DateTimeField(auto_now_add=True)
    kind = models.ForeignKey(FileKind, on_delete=models.CASCADE)
    document = models.FileField(upload_to=FileLocation, storage=private_fs)
    description = models.CharField(max_length=127, blank=True, null=True)
    access = models.CharField(max_length=1,
                              choices=ACCESS_CHOICES,
                              default='1')
    release_time = models.DateTimeField(null=True, blank=True)
    release_until = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        extra = self.description if self.description and len(
            self.description) > 0 else self.document
        return "{0} - {1}".format(self.kind, extra)

    def has_window(self):
        return self.release_time or self.release_until

    def in_window(self):
        now = datetime.datetime.now(ZoneInfo('UTC'))
        if self.release_time and now < self.release_time:
            return False
        if self.release_until and now > self.release_until:
            return False
        return True

    def has_access(self, request):
        if request.user.is_authenticated:
            return True
        if self.access == 'C':
            if not request.session['Claremont']:
                return False
        return self.in_window()

    @property
    def icon(self):
        ext = os.path.splitext(str(self.document))[1]
        ext = ext[1:].lower()
        mapping = {
            'xls': 'excel',
            'xlsx': 'excel',
            'doc': 'word',
            'docx': 'word',
            'jpg': 'jpg',
            'jpeg': 'jpg',
            'nb': 'nb',
            'pdf': 'pdf',
            'png': 'png',
            'tex': 'tex',
            'latex': 'tex',
            'txt': 'txt',
            'ipynb': 'ipynb',
            'html': 'link',
        }
        try:
            ico = mapping[ext]
        except:
            ico = 'unk'
        icon = "ico/{0}.png".format(ico)
        return icon

    @property
    def url(self):
        return urllib.parse.quote('/c/private/{0}'.format(self.document))

    @property
    def showrelease(self):
        try:
            y, m, d = self.release_time.year, self.release_time.month, self.release_time.day
            return f'{m}/{d}/{y}{self.release_time.strftime(" %H:%M")}'
        except:
            return ""

    def link(self, request):
        desc = self.description if (self.description and
                                    len(self.description) > 0) else self.document
        klass = "prof" if request.user.is_authenticated else "student"
        if self.has_window():
            klass += " inwindow" if self.in_window() else " outwindow"
        s = '<a href="{0}" class="{1}"><img src="{2}{3}" class="icon">&nbsp;{4}</a>'
        if self.has_window() and not self.in_window():
            s = s.replace("</a>", f" ({self.showrelease})</a>")
        # Let's add file mod date/time information
        mtime = Path(self.document.path).stat().st_mtime
        dt = datetime.datetime.fromtimestamp(
            mtime, ZoneInfo('America/Los_Angeles'))
        ds = dt.strftime("%m/%d/%y %H:%M")
        ds = re.sub(r"0(\d)", r"\1", ds)
        s = s.replace(
            "</a>", f'</a>&nbsp;&nbsp;<span class="fdate">({ds})</span>  ')
        url = s.format(self.url, klass, settings.STATIC_URL, self.icon, desc)
        return mark_safe(url)


class MeetingFile(SecureCourseFile):
    meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE)

# class CourseSiteFile(SecureCourseFile):
#    course_site = models.ForeignKey(CourseSite, on_delete=models.CASCADE)


def test():
    crs = Course.objects.get(order_field='p064')
    yr = 2025
    crec = CourseRecord.objects.get(course=crs, year=yr)
    csite = CourseSite.objects.get(course=crs)
    csite.make_calendar(crec, lectures="TR")
