from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.core.mail import EmailMessage
from django.utils.html import strip_tags
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from smtplib import SMTPException
from module_iam.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType
from .utils import RandomGenerator
# from twilio.rest import Client
from django.db.models import Q
import phonenumbers
from decimal import Decimal
from django.db.models import F
from django.db import transaction
from datetime import timedelta, time, datetime
from django.utils import timezone
import requests
from onesignal_sdk.client import Client as OneSignalClient
import logging

import onesignal
from onesignal.models import Notification
from onesignal.api import default_api

logger = logging.getLogger(__name__)


class EmailService:
    """
    A reusable class for sending emails

    Example:
    ```python
    email_service = EmailService(
        subject="Hello",
        to="recipient@example.com",
        from_email="sender@example.com",
    )
    email_service.set_html_body("<p>Hello, this is a test email!</p>")
    email_service.send()
    ```

    This class provides methods to set the subject, recipient(s), sender, and body of the email. 
    It also supports loading email content from templates and attaching files.
    """
    def __init__(self, subject=None, to=None, from_email=None):
        self.subject = subject
        self.to = to if isinstance(to, (list, tuple)) else [to]
        self.from_email = from_email
        self.body = None
        self.content_subtype = "html"

    def set_subject(self, subject):
        self.subject = subject

    def set_to(self, to):
        """
        Set the recipient email address or addresses.

        Parameters:
        - to (str or list): The recipient email address or a list of recipient email addresses.
        """
        self.to = to if isinstance(to, (list, tuple)) else [to]

    def set_from_email(self, from_email):
        self.from_email = from_email

    def set_text_body(self, body):
        """Set the plain text body of the email."""
        self.body = strip_tags(body)

    def set_html_body(self, html_body):
        """Set the HTML body of the email."""
        self.body = html_body

    def load_template(self, path=None, context={}):
        """
        Load an email template from a file and render it with the provided context.

        Parameters:
        - path (str): The path to the email template file.
        - context (dict): The context data to render the template.
        """
        if path is None:
            raise Exception("Email template path is not provided.")

        self.content_subtype = "html"
        self.body = render_to_string(path, context=context)

    def attach(self, file_path):
        """
        Attach a file to the email.

        Parameters:
        - file_path (str): The path to the file to be attached.
        """
        if not hasattr(self, 'attachments'):
            self.attachments = []
        self.attachments.append(file_path)

    def send(self):
        try:
            email = EmailMessage(
                subject=self.subject,
                body=self.body,
                to=self.to,
                from_email=self.from_email,
            )
            email.content_subtype = self.content_subtype

            if hasattr(self, 'attachments'):
                for attachment in self.attachments:
                    email.attach_file(attachment)

            email.send()
        except Exception as e:
            logger.error(f"Error sending email: {str(e)}")
class SMSError(Exception):
    def __init__(self, message, payload=None):
        self.message = message
        self.payload = payload

    def __str__(self):
        return str(self.message)


class SMSService:
    # def send(self, to: list, text: str):
    #     """
    #     Sends text sms to the given user(s).

    #     Parameters:
    #     to (list): list of phone numbers

    #     text (str): a text message.

    #     Return:
    #     True or False
    #     """

    #     # if settings.TESTING_ENV:
    #     #     logger.info(f"TESTING ENV SMS LOG : {text}")
    #     #     return True

    #     account_sid = settings.TWILIO_ACCOUNT_SID
    #     auth_token = settings.TWILIO_AUTH_TOKEN
    #     my_twilio_number = settings.MY_TWILIO_NUMBER

    #     client = Client(account_sid, auth_token)

    #     try:
    #         for number in to:
    #             logger.info("SENDING SMS TO " + str(number))
    #             message = client.messages.create(
    #                 from_=my_twilio_number, body=text, to=number
    #             )

    #     except Exception as e:
    #         logger.error(str(e))

    #         raise SMSError(message=str(e))

    def create_otp(self, principal: IAmPrincipal, otp_purpose: str):
        old_otp_change = IAmPrincipalOtp.objects.filter(principal=principal).update(is_used=True)
        otp = IAmPrincipalOtp.objects.create(
            principal=principal, otp_purpose=otp_purpose
        )
        otp.save()
        return otp.otp_code

    def send_otp(self, principal: IAmPrincipal, otp_purpose: str):
        """
        Sends otp to the given user.

        Parameters:
        user (User): User object
        otp_purpose (str) : a text that describe otp purpose

        Return:
        True or False
        """

        if not isinstance(principal, IAmPrincipal):
            raise Exception(
                f"parameter 'principal' required type of User object, Given {type(principal)} type object"
            )

        otp_code = self.create_otp(principal=principal, otp_purpose=otp_purpose)
        # below working will change as it is temporary purpose
        body = f"Your Nifty11 OTP is {otp_code}."

        print(body)

        phone_numbers = []

        try:
            parsed_number = phonenumbers.parse(str(principal.phone_no), None)
            if phonenumbers.is_valid_number(parsed_number):
                formatted_number = phonenumbers.format_number(
                    parsed_number, phonenumbers.PhoneNumberFormat.E164
                )
                phone_numbers.append(formatted_number)
            else:
                raise ValueError("Invalid phone number")
        except Exception as e:
            logger.warning(f"{e}")
            raise ValueError("Invalid phone number")

        if not phone_numbers:
            raise ValueError("Invalid phone number")

        print(f"phone number {type(phone_numbers)} {phone_numbers}")

        # self.send(phone_numbers, body)
        return otp_code

# by using office onesignal package onesignal-python-api
class OneSignalService:
    def __init__(self):

        # Get the OneSignal app key and user key from the environment variables
        self.configuration = onesignal.Configuration(

            app_key=settings.ONESIGNAL_APP_ID,
            api_key=settings.ONESIGNAL_REST_API_KEY
        )

        # Create an instance of the OneSignal API
        self.api_client = onesignal.ApiClient(self.configuration)
        self.api_instance = default_api.DefaultApi(self.api_client)

    def send_notification(self, headings, contents, include_player_ids=None):
        # Create a notification object using a dictionary
        notification = Notification(
            app_id=self.configuration.app_key,
            include_player_ids=include_player_ids,
            headings={"en": headings},
            contents={"en": contents}
        )
        try:
            # Send the notification
            response = self.api_instance.create_notification(
                notification=notification,
                async_req=True
            )
        except Exception as e:
            raise Exception("Generic OneSignal error: {}".format(e))
        print("complete service is succeesss")
        return response

# by using community packgae onesignal-sdk
class OneSignalNotificationService:

    """
    Class for sending notifications using the OneSignal API.

    Provides a convenient way to create and send notifications to OneSignal users,
    with features like targeting specific devices or segments, customizing notification content,
    and handling errors gracefully.

    **Parameters:**

    - **app_id** (str): Your OneSignal App ID.
    - **rest_api_key** (str): Your OneSignal REST API Key.
    - **user_auth_key** (str): Your OneSignal User Auth Key.

    **Keyword Arguments:**

    This method accepts additional keyword arguments (`**kwargs`) to customize the notification
    further, including:

    - `url` (str): URL to open when the notification is clicked.
    - `data` (dict): Custom data to be sent with the notification.
    - `buttons` (list): List of action buttons to display within the notification.
    - `send_after` (str): Timestamp for scheduling the notification.
    - `delayed_option` (dict): Option for delayed delivery (Android-specific).
    - `android_channel_id` (str): Channel ID for Android notifications.
    - `ios_sound` (str): Sound to play for iOS notifications.
    - `ios_badgeType` (str): Badge type for iOS notifications.
    - `ios_badgeCount` (int): Badge count for iOS notifications.
    - `ios_thread_id` (str): Thread ID to group notifications in iOS.
    - `android_background_layout` (str): Layout for background notifications on Android.
    - `android_group` (str): Group notification on Android.
    - `android_group_message` (str): Summary for grouped notifications on Android.
    - `android_group_summary` (str): Summary for grouped notifications on Android.
    - `android_led_color` (str): LED color for Android notifications.
    - `android_accent_color` (str): Accent color for Android notifications.
    - `android_visibility` (str): Visibility settings for Android notifications.

    **Example usage:**

    notification = OneSignalNotificationService()
    response = notification.send_notification(
        headings="Welcome",
        message="Thanks for signing up!",
        player_tokens=["PLAYER_TOKEN1", "PLAYER_TOKEN2"],
        url="https://yourwebsite.com/welcome",
        data={"user_id": 123},
    )
    """

    def __init__(self):
        self.config = OneSignalClient(
            app_id=settings.ONESIGNAL_APP_ID,
            rest_api_key=settings.ONESIGNAL_REST_API_KEY,
            user_auth_key=settings.ONESIGNAL_USER_AUTH_KEY
        )

        # Set up logging
        self.logger = logging.getLogger(__name__)

    def send_notification(self, headings, message, player_tokens=None, **kwargs):
        notification_obj = {
            "headings": {"en": headings},
            "contents": {"en": message},
            **kwargs
        }

        if player_tokens:
            notification_obj["include_player_ids"] = player_tokens

        try:
            response = self.config.send_notification(notification_obj)
            self.logger.info(f"Notification send successfully : {response}")
            return response
        except Exception as e:
            self.logger.error(f"OneSignal error {e}")
            raise Exception("Generic OneSignal error: {}".format(e))
