from vms import connect_to_vcenter, get_vm_info_and_save_to_db, update_vm_power_status
from flask import Flask, render_template, flash, send_from_directory, redirect, url_for
from db_manager import db, User, VirtualMachine, Stables
from flask_login import LoginManager, login_required, current_user
from routers.user_routers import user_blueprint
from routers.vm_routers import vm_blueprint
from flask_principal import Principal
from flask_socketio import SocketIO
from flask_migrate import Migrate
from flask_session import Session
from flask.cli import AppGroup
from dotenv import load_dotenv
from docx import Document
from io import BytesIO
import threading
import schedule
import requests
import logging
import redis
import time
import os
import re

# Создаем директорию для логов, если ее нет
log_dir = 'logs'
if not os.path.exists(log_dir):
    os.makedirs(log_dir)

# Настройки логирования
log_format = '%(name)s - %(levelname)s - %(message)s'
log_path = os.path.join(log_dir, 'app.log')
logging.basicConfig(filename=log_path, level=logging.INFO, format=log_format, filemode='w')

dotenv_path = os.path.join(os.path.dirname(__file__), '.env')

if os.path.exists(dotenv_path):
    load_dotenv(dotenv_path)
else:
    exit('[STOP SYSTEM STARTUP] >> Не обнаружен файл переменных окружения ".env". \n'
         'Файл должен располагаться на одном уровне с "app.py".')

app = Flask(__name__)
socketio = SocketIO(app)
login_manager = LoginManager()

app.register_blueprint(user_blueprint)
app.register_blueprint(vm_blueprint)

login_manager.init_app(app)

principal = Principal(app)

migrate = Migrate(app, db)

app.secret_key = os.environ.get('SECRET')

# Блок создания кастомных комманд

group_command_one = AppGroup('backtask')


@group_command_one.command('full')
def command():
    logging.info('Запустить в ручном режиме выполнение выполнения - full_upd')
    full_upd()


@group_command_one.command('power')
def command():
    logging.info('Запустить в ручном режиме выполнение выполнения - power_status_upd')
    power_status_upd()


@group_command_one.command('version')
def command():
    logging.info('Запустить в ручном режиме выполнение запуска - стабильный_upd')
    stables_upd()


app.cli.add_command(group_command_one)

# End block


if (os.environ.get('DB_TYPE')).lower() == "sqlite":
    app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.environ.get('DB_NAME')}.db"
elif (os.environ.get('DB_TYPE')).lower() == "postgresql":
    app.config['SQLALCHEMY_DATABASE_URI'] = (f"postgresql://{os.environ.get('DB_USER')}:{os.environ.get('DB_PASS')}@"
                                             f"{os.environ.get('DB_HOST')}:{os.environ.get('DB_PORT')}/"
                                             f"{os.environ.get('DB_NAME')}")
elif (os.environ.get('DB_TYPE')).lower() == "mysql":
    app.config['SQLALCHEMY_DATABASE_URI'] = (f"mysql+mysqlconnector://{os.environ.get('DB_USER')}:"
                                             f"{os.environ.get('DB_PASS')}@{os.environ.get('DB_HOST')}:"
                                             f"{os.environ.get('DB_PORT')}/{os.environ.get('DB_NAME')}")
else:
    exit('[DB ERROR] >> Неверно указаны настройки базы данных (параметр - DB_TYPE)!')

db.init_app(app)

if (os.environ.get('SESSION_TYPE')).lower() == 'redis':
    app.config['SESSION_TYPE'] = 'redis'
    app.config['SESSION_PERMANENT'] = True
    app.config['SESSION_USE_SIGNER'] = True
    app.config['SESSION_KEY_PREFIX'] = os.environ.get('SESSION_KEY_PREFIX')
    app.config['SESSION_REDIS'] = redis.StrictRedis(
        host=os.environ.get('REDIS_HOST'),
        port=os.environ.get('REDIS_PORT'),
        db=os.environ.get('REDIS_DB'),
        password=os.environ.get('REDIS_PASS')
    )
    app.config['PERMANENT_SESSION_LIFETIME'] = int(os.environ.get('SESSION_LIFETIME'))
elif (os.environ.get('SESSION_TYPE')).lower() == 'file':
    app.config['SESSION_TYPE'] = 'filesystem'
else:
    exit('[SESSION ERROR] >> Неверно указаны настройки сессии(параметр - SESSION_TYPE)!')

Session(app)


@app.route('/')
def index():
    total_vm = VirtualMachine.query.count()
    number_of_employees = VirtualMachine.query.filter_by(status="Занято").count()
    number_of_technical = VirtualMachine.query.filter_by(technical=True).count()
    quantity_for_tests = int(total_vm) - (int(number_of_technical) + int(number_of_employees))
    stables_version = Stables.query.all()
    return render_template('home.html', total_vm=total_vm,
                           number_of_employees=number_of_employees,
                           number_of_technical=number_of_technical,
                           quantity_for_tests=quantity_for_tests,
                           stables_version=stables_version)


@app.route('/favicon.ico')
def fav():
    return send_from_directory(os.path.join(app.root_path, 'static'), 'image/fav.ico')


@app.route('/about')
def about():
    return render_template('about.html')


@app.route('/admin')
@login_required
def admin():
    if current_user.is_admin:
        all_user = User.query.all()
        return render_template('admin.html', all_user=all_user)
    else:
        flash(f'Пользователь {current_user} не администратор!', 'danger')
        return redirect(url_for('index'))


@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, user_id)


@login_manager.unauthorized_handler
def unauthorized():
    flash('Доступ разрешен только авторизованным!', 'danger')
    return render_template('login.html')


vcenter_connections = [
    {"host": f"{os.environ.get('HYPER1_HOST')}", "user": f"{os.environ.get('HYPER1_USER')}",
     "password": f"{os.environ.get('HYPER1_PASS')}"},
    {"host": f"{os.environ.get('HYPER2_HOST')}", "user": f"{os.environ.get('HYPER2_USER')}",
     "password": f"{os.environ.get('HYPER2_PASS')}"},
    {"host": f"{os.environ.get('HYPER3_HOST')}", "user": f"{os.environ.get('HYPER3_USER')}",
     "password": f"{os.environ.get('HYPER3_PASS')}"},
    {"host": f"{os.environ.get('HYPER4_HOST')}", "user": f"{os.environ.get('HYPER4_USER')}",
     "password": f"{os.environ.get('HYPER4_PASS')}"}
]


def stables_upd():
    with app.app_context():
        url = "https://s3.printum.io/stablerefs/Printum%20software.docx"
        response = requests.get(url)
        docx_file = BytesIO(response.content)
        doc = Document(docx_file)
        pattern = re.compile(r'(\d+\.\d+\.\d+)')
        target_values = []
        for paragraph in doc.paragraphs:
            matches = pattern.findall(paragraph.text)
            if matches:
                target_values.extend(matches)

        monitoring_value = target_values[0]
        printmanager_value = target_values[2]

        existing_stables = Stables.query.first()

        if existing_stables:
            existing_stables.monitoring = monitoring_value
            existing_stables.printmanager = printmanager_value
            db.session.commit()
        else:
            new_version = Stables(monitoring=monitoring_value, printmanager=printmanager_value)
            db.session.add(new_version)
            db.session.commit()
    logging.info('Фоновая задача «stables_upd» выполнена успешно.')


def power_status_upd():
    with app.app_context():
        for connection in vcenter_connections:
            content = connect_to_vcenter(connection['host'], connection['user'], connection['password'])
            if content:
                update_vm_power_status(content, connection['host'])
    logging.info('Фоновая задача «power_status_upd» выполнена успешно.')


def full_upd():
    with app.app_context():
        for connection in vcenter_connections:
            content = connect_to_vcenter(connection['host'], connection['user'], connection['password'])
            if content:
                get_vm_info_and_save_to_db(content, connection['host'])
    logging.info('Фоновая задача «full_upd» выполнена успешно.')


# Настройки обработки фоновых задач
if (os.environ.get('DISABLING_TASK')).lower() == 'false':
    lock = threading.Lock()


    def run_tasks():
        while True:
            lock.acquire()
            schedule.run_pending()
            lock.release()
            time.sleep(1)


    schedule.every(int(os.environ.get('STABLES_UPDATE'))).minutes.do(stables_upd)
    schedule.every(int(os.environ.get('POWER_STATUS_UPDATE'))).minutes.do(power_status_upd)
    if (os.environ.get('PERFORMANCE')).lower() == 'period':
        schedule.every(int(os.environ.get('FULL_UPDATE'))).minutes.do(full_upd)
    elif (os.environ.get('PERFORMANCE')).lower() == 'time':
        schedule.every().day.at(f"{os.environ.get('HOUR_FULL_UPDATE')}").do(full_upd)
    else:
        logging.error("Неверно указано время или период выполнения полного обновления!")
        exit(1)

    thread = threading.Thread(target=run_tasks)
    thread.start()
else:
    logging.error("Выполнение фоновых задач отключено!!")

# Первичная инициализация БД при первом запуске
if not os.path.exists(".init") and os.path.exists(".create"):
    print("Первоначальная инициализация и загрузка данных в базу данных осуществляется...")
    try:
        full_upd()
        stables_upd()
        with open(".init", "w") as f:
            f.write("Первая инициализация БД прошла успешно")
    except Exception as e:
        logging.error(f"ОШИБКА при первой инициализации: {e}")

if __name__ == '__main__':
    socketio.run(app, debug=True, allow_unsafe_werkzeug=True)
