from typing import Dict, Any
from flask_smorest import Page
from sqlalchemy import (
    insert,
    update,
)
from functools import wraps
from flask import (
    make_response,
    jsonify,
    request,
    g,
    current_app
)
from collections.abc import MutableMapping
from abc import (
    ABC,
    abstractmethod
)
from typing import Any, Optional, Dict
from src.models import DatabaseContextManager
import hashlib
import smtplib, ssl
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os
from functools import wraps
import jwt
from src.models.models import User 
import random
import string


def generate_otp():
    """Generate a 6-digit OTP"""
    return ''.join(random.choices(string.digits, k=6))

def supervisor_auth_required(f):
    """Decorator to enforce authentication for document handler endpoints"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        token = request.cookies.get('admin_auth_token')
        
        if not token:
            return custom_response(
                success=False,
                data="Authentication required",
                status_code=401
            )
            
        try:
            payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])

            with DatabaseContextManager() as ctx:
                admin = ctx.session.query(User).filter(User.id == payload['sub']).first()

                if admin:
                    return f(*args, **kwargs)
                return custom_response(
                    success=False,
                    data="Authentication error, admin not found, the incidence will be reported to the site manager",
                    status_code=401
                )
            
        except jwt.ExpiredSignatureError:
            return custom_response(
                success=False,
                data="Authentication expired. Please log in again.",
                status_code=401
            )
        except jwt.InvalidTokenError:
            return custom_response(
                success=False,
                data="Invalid authentication token",
                status_code=401
            )
            
    return decorated_function

def send_email_with_attachment(sender_email, sender_password, receiver_email, subject, message, attachment_path=None):
    """
    Send emails using cPanel webmail SMTP server with optional attachments.
    
    Args:
        sender_email (str): The sender's email address
        sender_password (str): The sender's email password
        receiver_email (str): The recipient's email address
        subject (str): Email subject
        message (str): Email body in HTML format
        attachment_path (str, optional): Path to the file to attach. Defaults to None.
    """
    try:
        # Create email message
        email_message = MIMEMultipart()
        email_message['From'] = sender_email
        email_message['To'] = receiver_email
        email_message['Subject'] = subject

        # Attach the message body
        email_message.attach(MIMEText(message, 'html'))

        # Attach the file if provided
        if attachment_path:
            # Open the file in binary mode
            with open(attachment_path, 'rb') as attachment:
                # Create a MIMEBase object for the attachment
                part = MIMEBase('application', 'octet-stream')
                part.set_payload(attachment.read())

            # Encode the file in ASCII characters to send by email
            encoders.encode_base64(part)

            # Add header as key/value pair to attachment part
            part.add_header(
                'Content-Disposition',
                f'attachment; filename={os.path.basename(attachment_path)}'
            )

            # Attach the file to the email
            email_message.attach(part)

        # Create secure SSL/TLS context
        context = ssl.create_default_context()

        # Connect to the SMTP server using SSL
        with smtplib.SMTP_SSL(current_app.config['MAIL_SERVER'], int(current_app.config['MAIL_PORT']), context=context) as server:
            # Login to the server
            server.login(sender_email, sender_password)
            # Send email
            server.send_message(email_message)
        return True, "Email sent successfully"
    except smtplib.SMTPAuthenticationError:
        return False, "Authentication failed. Please check your email and password."
    except smtplib.SMTPException as e:
        return False, f"SMTP error occurred: {str(e)}"
    except FileNotFoundError:
        return False, f"Attachment file not found: {attachment_path}"
    except Exception as e:
        return False, f"An error occurred: {str(e)}"
    

def send_email(sender_email, sender_password, receiver_email, subject, message):
    """
    Send emails using cPanel webmail SMTP server
    
    Args:
        sender_email (str): The sender's email address
        sender_password (str): The sender's email password
        receiver_email (str): The recipient's email address
        subject (str): Email subject
        message (str): Email body in HTML format
    """
    try:
        # Create email message
        email_message = MIMEMultipart()
        email_message['From'] = sender_email
        email_message['To'] = receiver_email
        
        # Handle Unicode in subject
        try:
            email_message['Subject'] = subject
        except UnicodeEncodeError:
            # Fallback to ASCII if Unicode fails
            email_message['Subject'] = subject.encode('ascii', 'ignore').decode('ascii')
        
        # Handle Unicode in message content
        try:
            email_message.attach(MIMEText(message, 'html', 'utf-8'))
        except UnicodeEncodeError:
            # Fallback to ASCII if Unicode fails
            safe_message = message.encode('ascii', 'ignore').decode('ascii')
            email_message.attach(MIMEText(safe_message, 'html', 'ascii'))

        # Create secure SSL/TLS context
        context = ssl.create_default_context()

        # Connect to the SMTP server using SSL
        with smtplib.SMTP_SSL(current_app.config['MAIL_SERVER'], 465, context=context) as server:
            # Login to the server
            server.login(sender_email, sender_password)
            
            # Send email
            server.send_message(email_message)

        return True, "Email sent successfully"
        
    except smtplib.SMTPAuthenticationError:
        return False, "Authentication failed. Please check your email and password."
    except smtplib.SMTPException as e:
        return False, f"SMTP error occurred: {str(e)}"
    except UnicodeEncodeError as e:
        return False, f"Unicode encoding error: {str(e)}"
    except Exception as e:
        return False, f"An error occurred: {str(e)}"

def custom_response(success: bool, data=None, status_code=200):
    """
    Create a custom response in a standardized format.

    Args:
        success (bool): Indicates whether the response is successful.
        data (any): Custom data, usually a list of dictionaries or other JSON-serializable data.
        status_code (int): HTTP status code (default is 200).

    Returns:
        Response object: Flask response object.
    """
    response_body = {
        "success": success,
        "data": data or []  # Default to an empty list if data is None
    } if status_code == 200 or status_code == 201 else {
        "success": success,
        "message": data
    }
    response = make_response(jsonify(response_body), status_code)
    return response


def hash_password(password, salt=None):
    """Hashes a password using SHA-256."""
    password = password.encode('utf-8')
    salt = salt.encode('utf-8')
    hashed_password = hashlib.sha256(password + salt).hexdigest()
    return hashed_password

def check_password(password, hashed_password, salt):
    """Checks if the provided password matches the hashed password."""
    password = password.encode('utf-8')
    salt = salt.encode('utf-8')
    new_hashed_password = hashlib.sha256(password + salt).hexdigest()
    return new_hashed_password == hashed_password


class DatabaseTableMixin(MutableMapping, ABC):
    def __init__(self, table):
        self.table = table

    def __getitem__(self, item: int):
        with DatabaseContextManager() as contextmanager:
            response = contextmanager.session.query(self.table).filter_by(
                id=item
            ).first()
        return self[item]

    def __create_item__(self, value: Dict[str, Any]):
        with DatabaseContextManager() as contextmanager:
            statement = insert(self.table).values(
                **value
            )
            try:
                contextmanager.session.execute(statement)
                contextmanager.commit()
            except Exception as e:
                print(f"{e!r}")
            finally:
                contextmanager.commit()

    def __setitem__(self, key, value: Dict[str, str]) -> Dict[str, str]:
        with DatabaseContextManager() as contextmanager:
            statement = update(self.table).where(
                self.table.id == key
            ).values(**value)
            contextmanager.session.execute(statement)
            contextmanager.commit()
        
        return self[key]

    def __len__(self) -> int:
        with DatabaseContextManager() as contextmanager:
            return contextmanager.session.query(self.table).filter_by().count()

    def __iter__(self):
        # return elements from the cache
        with DatabaseContextManager() as contextmanager:
            for elements in contextmanager.session.query(self.table):
                yield elements

    def __delitem__(self, key):
        instance = self[key]

        with DatabaseContextManager() as contextmanager:
            contextmanager.session.delete(instance)
            contextmanager.commit()

    def filter(self, **kwargs):
        with DatabaseContextManager() as contextmanager:
            return contextmanager.session.query(self.table).filter_by(**kwargs)

    def __contains__(self, name) -> bool:
        for elements in iter(self):
            if elements.name == name:
                return True
            return False


class ApiABC(Page, ABC):
    def __init__(self, table):
        self.table = DatabaseTableMixin(table)

    @property
    def item_count(self):
        return len(self.table)
    
    @abstractmethod
    def get(self, id) -> Dict[str, Any]:
        pass
    
    @abstractmethod
    def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        pass
    
    @abstractmethod
    def update(self, payload) -> Dict[str, Any]:
        pass
    
    @abstractmethod
    def fetchAll(self) -> Dict[str, Any]:
        pass

    @abstractmethod
    def delete(self, id) -> Dict[str, Any]:
        pass
