import datetime
import re
import typing
from urllib.parse import urljoin

import requests
from django.conf import settings
from django.utils import dateparse, timezone
from icalendar import Calendar
from rest_framework import serializers
from rest_framework.request import Request

from apps.schedules.ical_utils import fetch_ical_file
from common.api_helpers.exceptions import BadRequest
from common.jinja_templater import apply_jinja_template
from common.jinja_templater.apply_jinja_template import JinjaTemplateWarning
from common.timezones import raise_exception_if_not_valid_timezone


class CurrentOrganizationDefault:
    """
    Utility class to get the current organization right from the serializer field.
    In pair with serializers.HiddenField gives an ability to create objects
    without overriding perform_create on the model, while respecting unique_together constraints.
    Example: organization = serializers.HiddenField(default=CurrentOrganizationDefault())
    """

    requires_context = True

    def __call__(self, serializer_field):
        self.organization = serializer_field.context["request"].auth.organization
        return self.organization

    def __repr__(self):
        return "%s()" % self.__class__.__name__


class CurrentTeamDefault:
    """
    Utility class to get the current team right from the serializer field.
    """

    requires_context = True

    def __call__(self, serializer_field):
        self.team = serializer_field.context["request"].user.current_team
        return self.team

    def __repr__(self):
        return "%s()" % self.__class__.__name__


class CurrentUserDefault:
    """
    Utility class to get the current user right from the serializer field.
    """

    requires_context = True

    def __call__(self, serializer_field):
        self.user = serializer_field.context["request"].user
        return self.user

    def __repr__(self):
        return "%s()" % self.__class__.__name__


def validate_ical_url(url):
    if url:
        if settings.BASE_URL in url:
            raise serializers.ValidationError("Potential self-reference")
        try:
            ical_file = fetch_ical_file(url)
            Calendar.from_ical(ical_file)
        except requests.exceptions.RequestException:
            raise serializers.ValidationError("Ical download failed")
        except ValueError:
            raise serializers.ValidationError("Ical parse failed")
        return url
    return None


"""
This utility function is for building a URL when we don't know if the base URL
has been given a trailing / such as reading from environment variable or user
input.  If the base URL is coming from a validated model field urljoin can be used
instead.  Do not use this function to append query parameters since a / is added
to the end of the base URL if there isn't one.
"""


def create_engine_url(path, override_base=None):
    base = settings.BASE_URL
    if override_base:
        base = override_base
    if not base.endswith("/"):
        base += "/"
    trimmed_path = path.lstrip("/")
    return urljoin(base, trimmed_path)


def get_date_range_from_request(request: Request) -> typing.Tuple[str, datetime.date, int]:
    """Extract timezone, starting date and number of days params from request.

    Used mainly for schedules and shifts API.
    """
    user_tz: str = request.query_params.get("user_tz", "UTC")
    raise_exception_if_not_valid_timezone(user_tz)

    date = timezone.now().date()
    date_param = request.query_params.get("date")
    if date_param is not None:
        try:
            date = dateparse.parse_date(date_param)
        except ValueError:
            raise BadRequest(detail="Invalid date format")
        else:
            if date is None:
                raise BadRequest(detail="Invalid date format")

    starting_date = date if request.query_params.get("date") else None
    if starting_date is None:
        # default to current week start
        starting_date = date - datetime.timedelta(days=date.weekday())

    try:
        days = int(request.query_params.get("days", 7))  # fallback to a week
    except ValueError:
        raise BadRequest(detail="Invalid days format")

    return user_tz, starting_date, days


def check_phone_number_is_valid(phone_number):
    return re.match(r"^\+\d{8,15}$", phone_number) is not None


def serialize_datetime_as_utc_timestamp(dt: datetime.datetime) -> str:
    return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


def valid_jinja_template_for_serializer_method_field(template):
    for _, val in template.items():
        try:
            apply_jinja_template(val, payload={})
        except JinjaTemplateWarning:
            # Suppress warnings, template may be valid with payload
            pass
