#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
import os

from django.core.files.base import ContentFile
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled

from pretix.base.i18n import language
from pretix.base.models import (
    CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
    OrderPosition,
)
from pretix.base.services.tasks import EventTask, ProfiledTask
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import register_ticket_outputs
from pretix.celery_app import app
from pretix.helpers.database import rolledback_transaction

logger = logging.getLogger(__name__)


def generate_orderposition(order_position: int, provider: str):
    order_position = OrderPosition.objects.select_related('order', 'order__event').get(id=order_position)

    with language(order_position.order.locale, order_position.order.event.settings.region):
        responses = register_ticket_outputs.send(order_position.order.event)
        for receiver, response in responses:
            prov = response(order_position.order.event)
            if prov.identifier == provider:
                filename, ttype, data = prov.generate(order_position)
                path, ext = os.path.splitext(filename)
                for ct in CachedTicket.objects.filter(order_position=order_position, provider=provider):
                    ct.delete()
                ct = CachedTicket.objects.create(order_position=order_position, provider=provider,
                                                 extension=ext, type=ttype, file=None)
                ct.file.save(filename, ContentFile(data))
                return ct.pk


def generate_order(order: int, provider: str):
    order = Order.objects.select_related('event').get(id=order)

    with language(order.locale, order.event.settings.region):
        responses = register_ticket_outputs.send(order.event)
        for receiver, response in responses:
            prov = response(order.event)
            if prov.identifier == provider:
                filename, ttype, data = prov.generate_order(order)
                if ttype == 'text/uri-list':
                    continue

                path, ext = os.path.splitext(filename)
                for ct in CachedCombinedTicket.objects.filter(order=order, provider=provider):
                    ct.delete()
                ct = CachedCombinedTicket.objects.create(order=order, provider=provider, extension=ext,
                                                         type=ttype, file=None)
                ct.file.save(filename, ContentFile(data))
                return ct.pk


@app.task(base=ProfiledTask)
def generate(model: str, pk: int, provider: str):
    with scopes_disabled():
        if model == 'order':
            return generate_order(pk, provider)
        elif model == 'orderposition':
            return generate_orderposition(pk, provider)


class DummyRollbackException(Exception):
    pass


def preview(event: int, provider: str):
    event = Event.objects.get(id=event)

    with rolledback_transaction(), language(event.settings.locale, event.settings.region):
        item = event.items.create(name=_("Sample product"), default_price=42.23,
                                  description=_("Sample product description"))
        item2 = event.items.create(name=_("Sample workshop"), default_price=23.40)

        from pretix.base.models import Order
        order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
                                    email='sample@pretix.eu',
                                    locale=event.settings.locale,
                                    expires=now(), code="PREVIEW1234", total=119)

        scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
        sample = {k: str(v) for k, v in scheme['sample'].items()}
        p = order.positions.create(item=item, attendee_name_parts=sample, price=item.default_price)
        s = event.subevents.first()
        order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)
        order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)

        InvoiceAddress.objects.create(order=order, name_parts=sample, company=_("Sample company"))

        responses = register_ticket_outputs.send(event)
        for receiver, response in responses:
            prov = response(event)
            if prov.identifier == provider:
                return prov.generate(p)


def get_tickets_for_order(order, base_position=None):
    positions = list(order.positions_with_tickets)
    if not positions:
        return []
    if not order.ticket_download_available:
        return []

    providers = [
        response(order.event)
        for receiver, response
        in register_ticket_outputs.send(order.event)
    ]
    tickets = []

    if base_position:
        # Only the given position and its children
        positions = [
            p for p in positions if p.pk == base_position.pk or p.addon_to_id == base_position.pk
        ]

    for p in providers:
        if not p.is_enabled:
            continue

        if p.multi_download_enabled and not base_position:
            try:
                if len(positions) == 0:
                    continue
                ct = CachedCombinedTicket.objects.filter(
                    order=order, provider=p.identifier, file__isnull=False
                ).last()
                if not ct or not ct.file:
                    retval = generate_order(order.pk, p.identifier)
                    if not retval:
                        continue
                    ct = CachedCombinedTicket.objects.get(pk=retval)
                tickets.append((
                    "{}-{}-{}{}".format(
                        order.event.slug.upper(), order.code, ct.provider, ct.extension,
                    ),
                    ct
                ))
            except:
                logger.exception('Failed to generate ticket.')
        else:
            for pos in positions:
                try:
                    ct = CachedTicket.objects.filter(
                        order_position=pos, provider=p.identifier, file__isnull=False
                    ).last()
                    if not ct or not ct.file:
                        retval = generate_orderposition(pos.pk, p.identifier)
                        if not retval:
                            continue
                        ct = CachedTicket.objects.get(pk=retval)

                    if ct.type == 'text/uri-list':
                        continue

                    if pos.subevent:
                        # Subevent date in filename improves accessibility e.g. for screen reader users
                        fname = "{}-{}-{}-{}-{}{}".format(
                            order.event.slug.upper(), order.code, pos.positionid,
                            pos.subevent.date_from.strftime('%Y_%m_%d'),
                            ct.provider, ct.extension
                        )
                    else:
                        fname = "{}-{}-{}-{}{}".format(
                            order.event.slug.upper(), order.code, pos.positionid,
                            ct.provider, ct.extension
                        )
                    tickets.append((
                        fname,
                        ct
                    ))
                except:
                    logger.exception('Failed to generate ticket.')
    return tickets


@app.task(base=EventTask, acks_late=True)
def invalidate_cache(event: Event, item: int=None, provider: str=None, order: int=None, **kwargs):
    qs = CachedTicket.objects.filter(order_position__order__event=event)
    qsc = CachedCombinedTicket.objects.filter(order__event=event)

    if item:
        qs = qs.filter(order_position__item_id=item)

    if provider:
        qs = qs.filter(provider=provider)
        qsc = qsc.filter(provider=provider)

    if order:
        qs = qs.filter(order_position__order_id=order)
        qsc = qsc.filter(order_id=order)

    for ct in qs:
        ct.delete()
    for ct in qsc:
        ct.delete()
