import smtplib
import requests
import ssl
import json
from email.message import EmailMessage
import pymongo
from pymongo.errors import ConnectionFailure, OperationFailure, PyMongoError
from bson.objectid import ObjectId # Import ObjectId for querying by _id

# --- Configuration Loading ---
def load_config_from_json(file_path="config.json"):
    """Loads configuration from a JSON file."""
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            config = json.load(f)
        print(f"Configuration loaded from {file_path}")
        return config
    except FileNotFoundError:
        print(f"Error: Configuration file '{file_path}' not found. Please create it.")
        exit(1)
    except json.JSONDecodeError:
        print(f"Error: Could not decode JSON from '{file_path}'. Check file format for errors.")
        exit(1)
    except Exception as e:
        print(f"An unexpected error occurred while loading config: {e}")
        exit(1)

# Load configuration at the start
CONFIG = load_config_from_json()

# Access variables from the nested configuration
SMPT_CONFIG = CONFIG.get("SMPT", {})
SMTP_SERVER = SMPT_CONFIG.get("SMPTSERVER")
SMTP_PORT = int(SMPT_CONFIG.get("PORT")) if SMPT_CONFIG.get("PORT") is not None else None
SENDER_EMAIL = SMPT_CONFIG.get("SENDER_EMAIL")
SENDER_PASSWORD = SMPT_CONFIG.get("SENDER_PASSWORD")

API_CONFIG = CONFIG.get("API", {})
BASE_URL = API_CONFIG.get("BASE_URL")
API_USERNAME = API_CONFIG.get("USERNAME")
API_PASSWORD = API_CONFIG.get("PASSWORD")

MONGODB_CONFIG = CONFIG.get("MONGODB", {})
MONGO_CONNECTION_STRING = MONGODB_CONFIG.get("MONGO_CONNECTION_STRING")
DATABASE_NAME = MONGODB_CONFIG.get("DATABASE_NAME")
COLLECTION_NAME = MONGODB_CONFIG.get("COLLECTION_NAME")

# Validate essential configuration (more robust check)
if not (SMTP_SERVER and SMTP_PORT and SENDER_EMAIL and SENDER_PASSWORD and
        BASE_URL and API_USERNAME and API_PASSWORD and
        MONGO_CONNECTION_STRING and DATABASE_NAME and COLLECTION_NAME):
    print("Error: One or more essential configuration variables are missing or invalid in config.json.")
    # Print which specific parts are missing for easier debugging
    missing_configs = []
    if not SMTP_SERVER: missing_configs.append("SMPT.SMPTSERVER")
    if not SMTP_PORT: missing_configs.append("SMPT.PORT")
    if not SENDER_EMAIL: missing_configs.append("SMPT.SENDER_EMAIL")
    if not SENDER_PASSWORD: missing_configs.append("SMPT.SENDER_PASSWORD")
    if not BASE_URL: missing_configs.append("API.BASE_URL")
    if not API_USERNAME: missing_configs.append("API.USERNAME")
    if not API_PASSWORD: missing_configs.append("API.PASSWORD")
    if not MONGO_CONNECTION_STRING: missing_configs.append("MONGODB.MONGO_CONNECTION_STRING")
    if not DATABASE_NAME: missing_configs.append("MONGODB.DATABASE_NAME")
    if not COLLECTION_NAME: missing_configs.append("MONGODB.COLLECTION_NAME")
    print(f"Missing/Invalid: {', '.join(missing_configs)}")
    exit(1)

# Create a default SSL context once for secure connections
SSL_CONTEXT = ssl.create_default_context()

# --- API Functions ---
def login_api():
    """Logs into the API and returns the JWT token."""
    payload = {"username": API_USERNAME, "password": API_PASSWORD}
    headers = {"Content-Type": "application/json"}
    try:
        response = requests.post(f"{BASE_URL}/auth/login", json=payload, headers=headers, timeout=10) # Add timeout
        response.raise_for_status()
        data = response.json().get("data") # Use .get() for safer access
        jwt = data.get("token") if data else None # Safely get token
        if jwt:
            print("API login successful.")
            return jwt
        else:
            print("API response missing 'data' or 'token' key.")
            return None
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error during API login: {e} - Response: {getattr(e.response, 'text', 'N/A')}")
        return None
    except requests.exceptions.Timeout:
        print("Timeout Error during API login: The server did not respond in time.")
        return None
    except requests.exceptions.ConnectionError:
        print("Connection Error during API login: Could not connect to the API server. Check URL and network.")
        return None
    except requests.exceptions.RequestException as e:
        print(f"An unexpected error occurred during API login: {e}")
        return None
    except json.JSONDecodeError:
        print(f"API response is not valid JSON for login: {response.text}")
        return None

def get_email_bodies_from_api(jwt):
    """Retrieves email body data from the API."""
    if not jwt:
        print("No JWT token available to get email bodies.")
        return []

    headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
    try:
        response = requests.get(f"{BASE_URL}/report/report-mail", headers=headers, timeout=10) # Add timeout
        response.raise_for_status()
        data = response.json().get("data") # Safely get data
        if isinstance(data, list):
            print(f"Successfully retrieved {len(data)} email bodies from API.")
            return data
        else:
            print("API response for report-mail is missing 'data' key or it's not a list.")
            return []
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error when getting email bodies: {e} - Response: {getattr(e.response, 'text', 'N/A')}")
        return []
    except requests.exceptions.Timeout:
        print("Timeout Error when getting email bodies: The server did not respond in time.")
        return []
    except requests.exceptions.ConnectionError:
        print("Connection Error when getting email bodies: Could not connect to the API server. Check URL and network.")
        return []
    except requests.exceptions.RequestException as e:
        print(f"An error occurred when getting email bodies: {e}")
        return []
    except json.JSONDecodeError:
        print(f"API response for getting email bodies is not valid JSON: {response.text}")
        return []

# --- SMTP Functions ---
def login_smtp():
    """Logs into the SMTP server and returns the server object."""
    server = None
    try:
        server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        server.starttls(context=SSL_CONTEXT)
        print("TLS connection established.")

        server.login(SENDER_EMAIL, SENDER_PASSWORD)
        print("Logged in to SMTP server successfully.")
        return server
    except smtplib.SMTPAuthenticationError as e:
        print(f"SMTP Authentication Error: {e}")
        print("Please check your email address and password/app password.")
        print("For Gmail, ensure 'Less secure app access' is OFF and you're using an App Password.")
        print("For Outlook, you might need to generate an App Password.")
        if server: server.quit()
        return None
    except smtplib.SMTPServerDisconnected as e:
        print(f"SMTP Server Disconnected Error: {e}")
        print("The SMTP server unexpectedly disconnected. Check network or server status.")
        if server: server.quit()
        return None
    except smtplib.SMTPException as e:
        print(f"SMTP Error: {e}")
        print("An error occurred during the SMTP transaction.")
        if server: server.quit()
        return None
    except Exception as e:
        print(f"An unexpected error occurred during SMTP login: {e}")
        if server: server.quit()
        return None

def send_email(smtp_server, receiver_email, subject, body):
    """Sends an HTML email using a pre-logged-in SMTP server."""
    if not smtp_server:
        print("SMTP server not initialized. Cannot send email.")
        return False

    msg = EmailMessage()
    msg["From"] = SENDER_EMAIL
    msg["Subject"] = subject
    
    msg.add_alternative(body, subtype="html") 

    if isinstance(receiver_email, str):
        receiver_email = [receiver_email]

    msg["To"] = ", ".join(receiver_email)
    recipients_for_log = ", ".join(receiver_email)

    print(f"Attempting to send email from {SENDER_EMAIL} to {recipients_for_log}...")

    try:
        smtp_server.send_message(msg, from_addr=SENDER_EMAIL, to_addrs=receiver_email)
        print(f"Email sent successfully to {recipients_for_log}!")
        return True
    except smtplib.SMTPRecipientsRefused as e:
        print(f"Recipient Refused Error: {e.recipients}")
        print(f"Email not sent to some recipients: {recipients_for_log}. Check recipient addresses.")
        return False
    except smtplib.SMTPException as e:
        print(f"SMTP Error during sending: {e}")
        print(f"Could not send email to {recipients_for_log}.")
        return False
    except Exception as e:
        print(f"An unexpected error occurred while sending email: {e}")
        print(f"Could not send email to {recipients_for_log}.")
        return False

# --- MongoDB Functions ---
def update_mongodb_document(query, new_values, upsert=False, multi=False):
    """
    Connects to MongoDB and updates documents in a specified collection.
    """
    client = None  # Initialize client to None for finally block
    try:
        # Establish a connection to MongoDB
        # The serverSelectionTimeoutMS helps prevent long waits if the server is unreachable
        client = pymongo.MongoClient(
            MONGO_CONNECTION_STRING, serverSelectionTimeoutMS=5000
        )

        # The ping command is cheap and does not require auth.
        # This will raise an exception if the connection fails.
        client.admin.command("ping")
        print("MongoDB connection successful!")

        # Access the specified database and collection
        db = client[DATABASE_NAME]
        collection = db[COLLECTION_NAME]

        print(
            f"Attempting to update document(s) in '{DATABASE_NAME}.{COLLECTION_NAME}'..."
        )
        print(f"Query: {query}")
        print(f"New Values: {new_values}")

        if multi:
            # Update multiple documents
            result = collection.update_many(query, new_values, upsert=upsert)
            print(
                f"Matched {result.matched_count} document(s) and modified {result.modified_count} document(s)."
            )
            if result.upserted_id:
                print(f"Upserted a new document with _id: {result.upserted_id}")
        else:
            # Update a single document
            result = collection.update_one(query, new_values, upsert=upsert)
            print(
                f"Matched {result.matched_count} document(s) and modified {result.modified_count} document(s)."
            )
            if result.upserted_id:
                print(f"Upserted a new document with _id: {result.upserted_id}")

        return result

    except ConnectionFailure as e:
        print(
            f"MongoDB Connection Error: Could not connect to server. Please check connection string and server status. Error: {e}"
        )
    except OperationFailure as e:
        print(
            f"MongoDB Operation Error: An error occurred during a database operation. Error: {e}"
        )
    except PyMongoError as e:
        print(f"PyMongo Error: An unexpected PyMongo error occurred: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
    finally:
        # Ensure the client connection is closed
        if client:
            client.close()
            print("MongoDB connection closed.")
    return None


# --- Main execution logic ---
if __name__ == "__main__":
    jwt_token = login_api()

    if jwt_token:
        # Call add_email_body_to_api only if necessary, e.g., if you're populating the DB
        # For a regular report sending script, you might only need to get_email_bodies_from_api
        email_objects = get_email_bodies_from_api(jwt_token)

        if email_objects:
            smtp_connection = login_smtp()
            if smtp_connection:
                for item in email_objects:
                    receivers = item.get("receivers")
                    subject = item.get("subject")
                    body = item.get("body")

                    if receivers and subject and body:
                        send_success = send_email(
                            smtp_connection, receivers, subject, body
                        )
                        if send_success:
                            try:
                                # Ensure you have an actual _id from your database for this to work
                                document_id_string = item.get("id")
                                query_by_id = {"_id": ObjectId(document_id_string)}
                                update_by_id_values = {"$set": {"IsSent": True}}
                                update_mongodb_document(
                                    query_by_id, update_by_id_values
                                )
                            except Exception as e:
                                print(
                                    f"Error in Example 5: Make sure the _id string is valid and exists. {e}"
                                )
                    else:
                        print(
                            f"Skipping email due to missing data in API response: {item}"
                        )
                smtp_connection.quit()  # Ensure SMTP connection is closed after all emails are sent
                print("SMTP connection closed.")
            else:
                print("Failed to establish SMTP connection. Cannot send emails.")
        else:
            print("No email bodies retrieved from API to send.")
    else:
        print("Failed to obtain JWT token. Cannot proceed with email operations.")
