import requests
import time
import pymysql
import smtplib
import email.utils
import traceback
import runpy
import os
import json
import logging
import calendar
import datetime as dt
from datetime import datetime
from pymysql.cursors import DictCursor
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from datetime import datetime, timedelta
from pymysql import OperationalError

class InternetSpeedChecker:
    def __init__(self,mode='production'):

        self.script_name = "Internet Checking"
        self.mode = mode
        self.failed_ips = []
        self.time_limit = 15
        self.country_limit = 5
        self.min_ratio_internet_speed = 90
        self.max_ratio_internet_speed = 100
        self.time_limit = 15
        self.total_data_usage = 0
        self.working_servers = 0
        self.not_working_servers = 0
        self.script_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.servers_above_100kbps = 0
        self.MAX_RETRIES = 3
        self.RETRY_DELAY = 2
        self.delete_log_days = 5
        self.log_directory = '/var/www/html/vpn-python-scripting_kr/internet_speed_logs'
        self.connect_ovpn_api = "http://localhost/vpn-python-scripting_kr/check_connection_internet.php"
        self.kill_ovpn_api = 'http://localhost/vpn-python-scripting_kr/kill_connection_internet.php'
        self.config = self.get_config()
        self.init_logging()
        self.remove_log_file()

    def get_config(self):
        """Define configurations based on mode."""
        DB_CONFIG = {
        'production': {
            "user": "ovpn",
            "password": "Vr3EqDO60Vs0N",
            "dbname": "ovpn",
            "host": "144.126.211.210",
            "ovpn_table_name": "ovpn_servers",
            "server_internet_speed_table_name": "server_internet_speed_logs",
            "country_table_name": "countries",
            "php_connection_path": "http://localhost/vpn-python-scripting_kr/check_connection_internet.php",
            # "php_connection_path": "http://52.36.182.131/vpn-python-scripting/check_connection.php",
            "api_data": "https://vpnquery.vpnproxymaster.app/api/v1/get_open_servers_list_py",
            "api_url": "https://vpnquery.vpnproxymaster.app/api/v1/update_ovpn_servers",
            "file_path_prefix": "http://144.126.211.210/ovpn/vpnGate/Ovpn/",
            "project_code": "1134",
            "ovpn_main_file_path":"Ovpn/"
        },
        'staging': {
            "user": "5Q98dbHvXQeG",
            "password": "0z4C5UmJzyu1",
            "dbname": "ovpn",
            "host": "52.38.82.122",
            "ovpn_table_name": "ovpn_servers",
            "server_internet_speed_table_name": "server_internet_speed_logs",
            "country_table_name": "countries",
            # "php_connection_path": "http://52.38.82.122/vpn-api-python/vpn_server_main_script_research/check_connection_internet.php",
            "php_connection_path": "http://localhost/vpn-python-scripting_kr/check_connection_internet.php",
            "api_data": "https://web.appethos.com/vpn-blue-staging/src/api/v1/get_open_servers_list_py",
            "api_url": "https://web.appethos.com/vpn-blue-staging/src/api/v1/update_ovpn_servers",
            "file_path_prefix": "http://52.38.82.122/vpn-api-python/vpn_server_main_script_research/Ovpn/",
            "project_code": "1134",
            "ovpn_main_file_path":"Ovpn/"
        },
        'local': {
            "user": "root",
            "password": "password",
            "dbname": "ovpn",
            "host": "localhost",
            "ovpn_table_name": "ovpn_servers",
            "server_internet_speed_table_name": "server_internet_speed_logs",
            "country_table_name": "countries",
            "php_connection_path": "http://localhost/vpn-python-scripting/check_connection.php",
            "api_data": "https://web.appethos.com/vpn-blue-staging/src/api/v1/get_open_servers_list_py",
            "api_url": "https://web.appethos.com/vpn-blue-staging/src/api/v1/update_ovpn_servers",
            "file_path_prefix": "http://172.16.17.158/vpn-python-scripting/Ovpn_new/",
            "project_code": "1134",
            "ovpn_main_file_path":"Ovpn/"
            }
        }
        
        return DB_CONFIG[self.mode]
    

    # def init_logging(self):
    #     """Initialize logging to a log file."""

    #     WORKING_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
    #     logs_dir = os.path.join(WORKING_DIRECTORY, 'internet_speed_logs')

    #     if not os.path.exists(logs_dir):
    #         os.makedirs(logs_dir)

    #     log_file_path = os.path.join(logs_dir, 'NEW_VPN_' + datetime.now().strftime('%Y-%m-%d') + '.log')
    #     print("log_file_path ------------",log_file_path)
    #     if not os.path.exists(log_file_path):
    #         logging.basicConfig(filename=log_file_path, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)
    #     else:
    #         logging.basicConfig(filename=log_file_path, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)

    def init_logging(self):
        """Initialize logging to both info and error log files."""
        WORKING_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
        
        logs_dir_info = os.path.join(WORKING_DIRECTORY, 'internet_speed_logs')
        if not os.path.exists(logs_dir_info):
            os.makedirs(logs_dir_info)

        logs_dir_error = os.path.join(WORKING_DIRECTORY, 'internet_speed_error_logs')
        if not os.path.exists(logs_dir_error):
            os.makedirs(logs_dir_error)

        log_file_path_info = os.path.join(logs_dir_info, 'NEW_VPN_' + datetime.now().strftime('%Y-%m-%d') + '.log')
        log_file_path_error = os.path.join(logs_dir_error, 'ERROR_VPN_' + datetime.now().strftime('%Y-%m-%d') + '.log')

        logging.basicConfig(filename=log_file_path_info, 
                            format='%(asctime)s - %(levelname)s - %(message)s', 
                            datefmt='%Y-%m-%d %H:%M:%S', 
                            level=logging.INFO)

        error_handler = logging.FileHandler(log_file_path_error)
        error_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', 
                                                     datefmt='%Y-%m-%d %H:%M:%S'))
        error_handler.setLevel(logging.ERROR)
        
        logging.getLogger().addHandler(error_handler)


    
    def get_database_connection(self):
        """Establish a database connection."""
        return pymysql.connect(
            host=self.config["host"],
            user=self.config["user"],
            password=self.config["password"],
            database=self.config["dbname"],
            cursorclass=DictCursor
        )
    
    # SEND_EXEPTION_MAIL_FUNTION
    def sendExceptionMail(self,exception_msg,country_count):
        # Replace these variables with your own values
        sender_email = "rolaxsergio09@gmail.com"
        receiver_emails = ["kriyans.p@upsquare.in","saurav.b@upsquare.in","vivek.f@upsquare.in"]
        password = "kuojxnfblltvlrro"
        if self.mode == 'staging':
            exception_title = "Internet_Script - Staging"
        elif self.mode == 'production':
            exception_title = "Internet_Script"
        else:
            exception_title = "Internet_Script - Local"
        exception_subject = "Country limit warning"
        
        # Create a MIME object
        message = MIMEMultipart()
        message["From"] = email.utils.formataddr((exception_title, sender_email))
        message["To"] = ", ".join(receiver_emails) 
        message["Subject"] = f"{exception_subject}"  

        # Add the body of the email
        body = f"""
            <html>
                <body>
                    <p style="font-size: 20px"><strong>Script has less server <u>{self.script_name}</u></strong></p>
                    <p style="font-size: 20px"><strong>Country count is : </strong>{country_count}</p>
                    <p style="font-size: 20px"><strong> Exception message: </strong></p>
                </body>
            </html>
            """ 
        message.attach(MIMEText(body, "html"))

        body_exception = exception_msg

        message.attach(MIMEText(body_exception, "plain"))
        try:
            # Establish a connection to the Gmail SMTP server
            with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
                server.login(sender_email, password)

                # Send the email
                server.sendmail(sender_email, receiver_emails, message.as_string())

            # print("Exception report email sent successfully!")
            logging.info("Countires report email sent successfully!")

        except Exception as e:
            excp_mail = traceback.format_exc()
            # print(excp_mail)
            logging.error(f"Failed to send exception report email: {e}")
            # print(f"Failed to send exception report email: {e}")


    def ovpnConnected(self,filepath):
        """Check if VPN connection is active."""

        # url = "http://52.38.82.122/vpn-api-python/vpn_server_main_script_research/check_connection_internet.php"
        payload = {"filepath": filepath}

        response = requests.post(self.connect_ovpn_api, data=payload)

        if response.status_code == 200:
            return response.json().get("isConnected", False)
        else:
            return f"Error: {response.status_code}, {response.text}"
        

    def download_file(self,url, chunk_size=1024, duration=10):
        try:
            response = requests.get(url, stream=True,timeout=30)
            total_size = int(response.headers.get('content-length', 0))
            logging.info(f'total_size : {total_size/(1024 * 1024)}')
            downloaded_size = 0
            start_time = time.time()
            
            for data in response.iter_content(chunk_size):
                downloaded_size += len(data)
                elapsed_time = time.time() - start_time
                if elapsed_time > duration:
                    break  # Stop downloading after the specified duration
                if elapsed_time > 0:  # Avoid division by zero
                    download_speed = (downloaded_size / elapsed_time) / (1024 * 1024)  # Convert to Mbps
                    # print(f"Downloaded {downloaded_size / 1024 / 1024:.2f} MB at {download_speed:.2f} Mbps", end='\r')
                else:
                    return -1
            ds = downloaded_size/(1024 * 1024)
            rounded_speeds = round(ds, 3)
            self.total_data_usage += rounded_speeds
            logging.info(f'downloaded_size : {rounded_speeds}')
            elapsed_time = time.time() - start_time
            download_speed = (downloaded_size / elapsed_time) / (1024 * 1024)  # Convert to Mbps
            return download_speed
        
        except requests.exceptions.ConnectionError:
        # logger.error("Connection error: Unable to download the file. Network is unreachable.")
        # print("Connection error: Unable to download the file. Network is unreachable.")
            return -1
        except requests.exceptions.ReadTimeout:
            # print("Read timeout error: Server took too long to send data.")
            return -1
        except requests.exceptions.Timeout:
            # print("Timeout error: Server did not respond in time.")
            return -1
        
    
    def kill_ovpn(self):
        return requests.get(self.kill_ovpn_api)
        
    
    def run_speedtest(self):
        """Run a speedtest and return the results."""
        download_url = "http://speedtest.atlanta.linode.com/100MB-atlanta.bin"
        # download_url = "https://testfile.org/1.3GBiconpng"
        download_speed = self.download_file(download_url, duration=self.time_limit)
    
        kill_vpn = self.kill_ovpn()
        if kill_vpn:
            logging.info("VPN Disconnected...")

        return download_speed
    
    def remove_log_file(self):

        cutoff_date = datetime.now() - timedelta(days=self.delete_log_days)

        for filename in os.listdir(self.log_directory):
            if filename.endswith('.log'):
                file_path = os.path.join(self.log_directory, filename)
                
                file_mod_time = datetime.fromtimestamp(os.path.getmtime(file_path))
                
                if file_mod_time < cutoff_date:
                    os.remove(file_path)

        print("Cleanup complete.")


    # def execute_sql_query(self, query, return_type = None):
    #     db = self.get_database_connection()
    #     cursor = db.cursor(DictCursor)

    #     cursor.execute(query)
    #     result = None

    #     if return_type == 'array':
    #         result = cursor.fetchall()
    #     elif return_type == 'row':
    #         result = cursor.fetchone()
    #     else:
    #         db.commit()

    #     cursor.close()
    #     db.close()
    #     return result
    

    def execute_sql_query(self, query, return_type=None):
        for attempt in range(self.MAX_RETRIES):
            try:
                db = self.get_database_connection()
                cursor = db.cursor(DictCursor)

                cursor.execute(query)
                result = None

                if return_type == 'array':
                    result = cursor.fetchall()
                elif return_type == 'row':
                    result = cursor.fetchone()
                else:
                    db.commit()
                cursor.close()
                db.close()
                
                return result

            except OperationalError as e:
                logging.error(f"Database operation failed on attempt {attempt + 1} with error: {e}")
                if attempt < self.MAX_RETRIES - 1:
                    kill_vpn = self.kill_ovpn()
                    if kill_vpn:
                        if kill_vpn.status_code == 200:
                            logging.info('VPN Disconnected...')
                    logging.info(f"Retrying in {self.RETRY_DELAY} seconds...")
                    time.sleep(self.RETRY_DELAY)
                else:
                    logging.error("Max retries reached. Could not execute the SQL query.")
                    raise e

            finally:
                try:
                    if cursor:
                        cursor.close()
                    if db:
                        db.close()
                except:
                    pass

    
    def perform_main_tasks(self):
        """Main function to execute speed tests and log results."""

        db = None
        cursor = None
        try:
            db = self.get_database_connection()
            cursor = db.cursor(DictCursor)

            serverdata_query = f"""SELECT server_id, ip_address, ovpn,country_code FROM `{self.config['ovpn_table_name']}` WHERE connect_percentage BETWEEN {self.min_ratio_internet_speed} AND {self.max_ratio_internet_speed}"""

            # cursor.execute(serverdata_query)
            # servers = cursor.fetchall()
            servers = self.execute_sql_query(serverdata_query, "array")
            # print(servers)
            logging.info(f"total server count is : {len(servers)}")

            countries = set()
            total_countries = set()
            for server in servers:
                ip_address =server["ip_address"]
                server_id =server["server_id"]
                country_code = server['country_code']

                total_countries.add(country_code)

                ovpn = server["ovpn"]
                filepath = self.config['ovpn_main_file_path'] + str(server_id) + "_" + ip_address + ".ovpn"
                # print("ovpn filepath", filepath)
                check_file = os.path.exists(filepath)

                if check_file == False:
                    with open(filepath, "w") as fh:
                        fh.write(ovpn)
                
                response = self.ovpnConnected(filepath)
                
                if response == True:
                    # print('True')
                    logging.info('--------------------------------------')
                    logging.info('True')
                    download_speed = self.run_speedtest()
                    rounded_speed = round(download_speed, 3)

                    # print('last_speed',rounded_speed)
                    logging.info(f'Country : {country_code} , ip_address : {ip_address}')
                    logging.info(f'last_speed_{rounded_speed}')

                    time.sleep(10)
                    if rounded_speed == -1:
                        self.not_working_servers += 1 
                        dt = time.gmtime()
                        ts = calendar.timegm(dt)

                        inactivate_query = "UPDATE {} SET is_active = {}, updated_at = '{}' WHERE ip_address = '{}'".format(self.config['ovpn_table_name'],0,ts,ip_address)
                        print(inactivate_query)

                        self.execute_sql_query(inactivate_query)
                        # cursor.execute(inactivate_query)
                        # db.commit()
                        self.failed_ips.append(ip_address)
                    else:
                        self.working_servers += 1
                    
                    if rounded_speed >= 0.100:
                        logging.info('This server is above 100 kbps')
                        self.servers_above_100kbps += 1
                        countries.add(server["country_code"])
                    
                    sql = f"UPDATE {self.config['ovpn_table_name']} SET internet_speed = {rounded_speed} WHERE ip_address = '{ip_address}'"
                    # print(sql)
                    logging.info(f'countries : {countries}')

                    self.execute_sql_query(sql)
                    # cursor.execute(sql)
                    # db.commit()
                    logging.info('Updated')
                    
                    time.sleep(15)
                else:
                    self.not_working_servers += 1
                    logging.info('False')
            
            print(self.total_data_usage)
            data_gb = self.total_data_usage / 1024
            formatted_data_gb = round(data_gb, 3)
            logging.info(f'Total data usage is : {self.total_data_usage} kbps')
            logging.info(f'Total data usage is : {formatted_data_gb} gb')
            # save_ips_to_json(failed_ips)

            if len(countries) <= self.country_limit:
                self.sendExceptionMail('Country is low more than limit : ',len(countries))

            connect_per = f"{self.min_ratio_internet_speed}-{self.max_ratio_internet_speed}"
            script_end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            start_date = datetime.strptime(self.script_start_time, '%Y-%m-%d %H:%M:%S')
            end_date = datetime.strptime(script_end_time, '%Y-%m-%d %H:%M:%S')

            time_difference = end_date - start_date
            hours_difference = time_difference.total_seconds() / 3600
            ds_time = round(hours_difference,4)

            print('hours_difference----------',hours_difference)

            usage_sql = f"INSERT INTO {self.config['server_internet_speed_table_name']} (total_data_usage_gb,working_servers,non_working_servers,total_servers,total_countries,start_date,end_date,total_time,connect_percentage,countries_above_100kbps,servers_above_100kbps) VALUES ({formatted_data_gb},{self.working_servers},{self.not_working_servers},{self.working_servers}+{self.not_working_servers},{len(total_countries)},'{self.script_start_time}','{script_end_time}',{ds_time},'{connect_per}',{len(countries)},{self.servers_above_100kbps})"
            print(usage_sql)
            logging.info(f'total countries : {countries}')
            self.execute_sql_query(usage_sql)
            logging.info('Data inserted')

        except Exception as e:

            data_gb = self.total_data_usage / 1024
            formatted_data_gb = round(data_gb, 3)
            logging.info(f'Total data usage is : {self.total_data_usage} kbps')
            logging.info(f'Total data usage is : {formatted_data_gb} gb')
            # save_ips_to_json(failed_ips)

            connect_per = f"{self.min_ratio_internet_speed}-{self.max_ratio_internet_speed}"
            script_end_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            start_date = datetime.strptime(self.script_start_time, '%Y-%m-%d %H:%M:%S')
            end_date = datetime.strptime(script_end_time, '%Y-%m-%d %H:%M:%S')

            time_difference = end_date - start_date
            hours_difference = time_difference.total_seconds() / 3600
            ds_time = round(hours_difference,4)

            data_dict = {
                'total_data_usage_gb': formatted_data_gb,
                'working_servers': self.working_servers,
                'non_working_servers': self.not_working_servers,
                'total_servers': self.working_servers + self.not_working_servers,
                'total_countries': len(total_countries),
                'start_date': self.script_start_time,
                'end_date': script_end_time,
                'total_time': ds_time,
                'connect_percentage': connect_per,
                'countries_above_100kbps': len(countries),
                'servers_above_100kbps': self.servers_above_100kbps
            }

            script_error_time  = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            excp = traceback.format_exc()
            # print("exp------", excp)
            logging.error(str(data_dict))
            # self.sendExceptionMail(excp)
            # runpy.run_path(path_name="check_server_speed.py")

    def run(self):
        logging.info("Script started.")
        self.perform_main_tasks()
        logging.info("Script completed.")


if __name__ == "__main__":
    checker = InternetSpeedChecker(mode='production')
    checker.run()