# pentabarf.py
#
# Copyright 2020-2025 Fabio Comuni, et al.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from typing import Optional

import datetime
import time
import sqlite3
import xml.etree.ElementTree as ET
from gettext import gettext as _

from dateutil.tz import UTC, tzlocal

from .exceptions import InvalidFormatException
from .. import local
from ..models import Meta
from ..utils import bind_list


def _local_to_utc_to_unset(dt):
    dt = dt.replace(tzinfo=tzlocal())
    dt = dt.astimezone(UTC)
    return dt.replace(tzinfo=None)


def _get_text(root: Optional[ET.Element], nodename: str) -> str:
    text = ""
    if root is not None:
        node = root.find(nodename)
        if node is not None and node.text is not None:
            text = node.text
    return text


def import_pentabarf(xmlstr: str, url: str):
    """Import data from Pentabarf XML

    As far I can tell, Pentabarf XML doesn't declare timezone.
    We will import dates in local timezone, then convert to UTC, then
    remove timezone info to store dates in db (because sqlite3 cries otherwise)
    """
    root = ET.fromstring(xmlstr)
    if root.tag != "schedule":
        raise InvalidFormatException(_("Invalid pentabarf format"))

    econference = root.find("conference")
    base_url = _get_text(econference, "base_url")
    with Meta() as m:
        m.url = url
        m.last_update = time.time()  # timestamp
        m.title = _get_text(econference, "title")
        m.start = _get_text(econference, "start")
        m.end = _get_text(econference, "end")
        m.venue = _get_text(econference, "venue")
        m.days = _get_text(econference, "days")
        m.city = _get_text(econference, "city")

    _db = local.getDb()

    found_event_ids = []
    found_person_ids = []

    for eday in root.iter('day'):
        date = eday.attrib['date']
        # found in sfscon. events nested in <room name=""> element
        # if we find 'day > room', we parse children events using
        # room name from room elements
        rooms = eday.findall("room")
        if rooms:
            for eroom in rooms:
                room = eroom.attrib['name']
                feids, fpids = _handle_events(_db, eroom, base_url, date, room)
                found_event_ids += feids
                found_person_ids += fpids
        else:
            # if we don't find 'day > room', we then parse as 'day > event'
            found_event_ids, found_person_ids = _handle_events(_db, eday, base_url, date)

    # cleanup removed events and persons
    _db.execute(f"""DELETE FROM persons WHERE id NOT IN ({bind_list(found_person_ids)})""", found_person_ids)
    _db.execute(f"""DELETE FROM events WHERE id NOT IN ({bind_list(found_event_ids)})""", found_event_ids)
    _db.execute(f"""DELETE FROM fts_event WHERE event_id NOT IN ({bind_list(found_event_ids)})""", found_event_ids)

    _db.commit()


def _handle_events(
        _db: sqlite3.Connection,
        root: ET.Element,
        base_url: str,
        date: str,
        room: Optional[str] = None) -> tuple[list, list]:

    found_event_ids = []
    found_person_ids = []

    for eevent in root.iter('event'):
        fulltextsearch = []
        # found in sfscon xml. event 'id' changes,
        # while unique_id not.
        # we use unique_id if is set
        if 'unique_id' in eevent.attrib:
            eventid = eevent.attrib['unique_id']
        else:
            eventid = eevent.attrib['id']
        found_event_ids.append(eventid)

        tstart = _get_text(eevent, 'start').strip()[:5]
        start = _local_to_utc_to_unset(
            datetime.datetime.strptime(date + " " + tstart, "%Y-%m-%d %H:%M")
        )
        end = _get_text(eevent, 'duration').split(":")
        end = start + datetime.timedelta(hours=int(end[0]), minutes=int(end[1]))
        evtdate = datetime.datetime.strptime(date, "%Y-%m-%d").date()
        if room is None:
            room = _get_text(eevent, 'room')
        slug = _get_text(eevent, 'slug')
        title = _get_text(eevent, 'title')
        subtitle = _get_text(eevent, 'subtitle')
        track = _get_text(eevent, 'track')
        evtype = _get_text(eevent, 'type')
        abstract = _get_text(eevent, 'abstract')
        description = _get_text(eevent, 'description')
        persons = []
        for e in eevent.iter('person'):
            if e.text is None:
                continue
            name = e.text
            id = e.attrib.get('id', name)
            org = e.attrib.get('organizzation', '')
            thumbnail = e.attrib.get('thumbnail', '')
            if thumbnail and thumbnail.startswith("/"):
                thumbnail = base_url + thumbnail
            bio = e.attrib.get('bio', '')
            url = e.attrib.get('url', '')
            if url and url.startswith("/"):
                url = base_url + url
            persons.append((
                id, name, org, thumbnail, bio, url
            ))
            found_person_ids.append(id)

        fulltextsearch += [s for s in [title, subtitle, abstract, description, room, track] if s is not None]
        fulltextsearch += [p[1] for p in persons]

        links = []
        for e in eevent.iter('link'):
            if e.text is None:
                continue
            name = e.text
            href = e.attrib.get('href', "")
            if not href:
                href = name
            if base_url and href.startswith("/"):
                href = base_url + href
            links.append((eventid, href, name))

        for e in eevent.iter('attachment'):
            if e.text is None:
                continue
            name = e.text
            if e.attrib.get('type', False):
                name = '{} ({})'.format(name, e.attrib['type'])
            href = e.attrib.get('href', "")
            if not href:
                href = name
            if base_url and href.startswith("/"):
                href = base_url + href
            links.append((eventid, href, name))

        _db.execute("""INSERT OR REPLACE INTO events
                        (id, date, start, end, room, slug, title, subtitle, track, type, abstract, description, starred)
                        VALUES (?,?,?,?,?,?,?,?,?,?,?,?, (SELECT starred FROM events WHERE id=?))""",
                    (eventid, evtdate, start, end, room, slug, title, subtitle, track, evtype, abstract, description, eventid))
        _db.executemany("INSERT OR REPLACE INTO persons (id, name, organization, thumbnail, bio, url) VALUES (?, ?, ?, ?, ?, ?)", persons)
        _db.executemany("""INSERT OR REPLACE INTO event_person (event_id, person_id)
                            VALUES (?, ?) ON CONFLICT DO NOTHING""",
                        [(eventid, p[0]) for p in persons])
        _db.executemany("""INSERT OR REPLACE INTO links
                            (event_id, href, name) VALUES (?,?,?)""", links)

        fulltextsearchstr = ' '.join(fulltextsearch)
        _db.execute("""INSERT OR REPLACE INTO fts_event (event_id, text) VALUES (?, ?)""", (eventid, fulltextsearchstr))

    return found_event_ids, found_person_ids
