#!/usr/bin/env python3
import os
import shutil
import subprocess
import datetime
import logging

try:
    from colorama import init, Fore, Style
    init(autoreset=True)
except ImportError:
    class Dummy:
        def __getattr__(self, attr):
            return ''
    Fore = Style = Dummy()
    def init(*args, **kwargs):
        pass
    print("⚠ colorama не установлен, используется не цветной вывод.")

# === Утилита для вывода этапов ===
def step(message, status="progress"):
    symbols = {
        "progress": Fore.CYAN + "[→]" + Style.RESET_ALL,
        "success": Fore.GREEN + "[✔]" + Style.RESET_ALL,
        "error": Fore.RED + "[✘]" + Style.RESET_ALL,
        "warning": Fore.YELLOW + "[!]" + Style.RESET_ALL
    }
    print(f"{symbols.get(status, '[ ]')} {message}")

    if status == "error":
        logging.error(message)
    elif status == "warning":
        logging.warning(message)
    elif status == "success":
        logging.info(message)
    else:
        logging.debug(message)

# === Пути и переменные ===
SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
SCRIPT_CONFIGS_DIR = os.path.join(SCRIPT_DIRECTORY, 'configs')
SCRIPT_IMAGES_DIR = os.path.join(SCRIPT_DIRECTORY, 'images')
APP_DIR = '/opt/mint/turn'
LOG_DIR = '/opt/mint/logs'
BACKUP_DIR = os.path.join(APP_DIR, 'backup')
VERSION_FILE_SRC = os.path.join(SCRIPT_CONFIGS_DIR, 'version')
VERSION_FILE_DST = os.path.join(APP_DIR, 'configs', 'version')
DOCKER_COMPOSE_SRC = os.path.join(SCRIPT_CONFIGS_DIR, 'docker-compose.yml')
DOCKER_COMPOSE_DST = os.path.join(APP_DIR, 'docker-compose.yml')
ENV_FILE_SRC = os.path.join(SCRIPT_CONFIGS_DIR, '.env')
ENV_FILE_DST = os.path.join(APP_DIR, '.env')
LOGROTATE_DIR = os.path.join(SCRIPT_DIRECTORY, 'mint_logrotate')
LOGROTATE_SCRIPT = os.path.join(LOGROTATE_DIR, 'setup_logrotate.py')

DOCKER_IMAGES = ['mint_turn']
CONTAINERS = ['mint_turn']
BASE_DIRS = [
    '/opt/mint/turn/configs/mint_turn/keys',
    '/opt/mint/scripts'
]

LOG_FILE = os.path.join(LOG_DIR, 'turn_setup.log')

# === Логирование ===
os.makedirs(LOG_DIR, exist_ok=True)
logging.basicConfig(filename=LOG_FILE, level=logging.INFO,
                    format='%(asctime)s:%(levelname)s:%(message)s')

# === Вспомогательные: версия ===
def get_new_version():
    try:
        with open(VERSION_FILE_SRC, 'r') as f:
            return f.read().strip()
    except Exception:
        return "0.0.0"

def get_installed_version():
    try:
        with open(VERSION_FILE_DST, 'r') as f:
            return f.read().strip()
    except Exception:
        return "0.0.0"

# === Проверка Docker ===
def check_docker_installed():
    try:
        subprocess.run(['docker', '--version'], check=True,
                       stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        step("Docker установлен", "success")
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        step("Docker не установлен или недоступен в PATH", "error")
        return False

# === Установка mint_logrotate ===
def setup_logrotate():
    if not os.path.exists(LOGROTATE_SCRIPT):
        step("Компонента logrotate не найдена, пропускаем установку", "warning")
        return False
    
    try:
        step("Начата установка компоненты logrotate", "progress")
        subprocess.run(['python3', LOGROTATE_SCRIPT, '--auto'], check=True)
        step("Установка logrotate завершена", "success")
        return True
    except subprocess.CalledProcessError as e:
        step(f"Ошибка установки logrotate: {e}", "error")

class EnvironmentSetup:
    @staticmethod
    def set_timezone():
        timezones = {
            '1': 'Europe/Kaliningrad',
            '2': 'Europe/Moscow',
            '3': 'Europe/Samara',
            '4': 'Asia/Yekaterinburg',
            '5': 'Asia/Omsk',
            '6': 'Asia/Krasnoyarsk',
            '7': 'Asia/Irkutsk',
            '8': 'Asia/Yakutsk',
            '9': 'Asia/Vladivostok',
            '10': 'Asia/Magadan',
            '11': 'Asia/Kamchatka'
        }
        while True:
            print("\nВыберите ваш часовой пояс:")
            for num, tz in timezones.items():
                print(f"{num}. {tz}")
            choice = input("Введите номер вашего часового пояса: ").strip()
            if choice not in timezones:
                print("Неверный выбор. Попробуйте еще раз.")
                continue
            timezone = timezones[choice]
            try:
                subprocess.run(['timedatectl', 'set-timezone', timezone], check=True)
                step(f"Часовой пояс установлен: {timezone}", "success")
                return timezone
            except subprocess.CalledProcessError as e:
                step(f"Ошибка при установке часового пояса: {e}", "error")
                return None

    @staticmethod
    def update_env_file(env_file_dst, env_file_src):
        try:
            with open(env_file_dst, 'r') as file:
                current_lines = file.read().splitlines()
            with open(env_file_src, 'r') as file:
                template_lines = file.read().splitlines()

            current_vars = {}
            for line in current_lines:
                if '=' in line and not line.strip().startswith('#'):
                    key, value = line.split('=', 1)
                    current_vars[key] = value

            new_env = []
            for line in template_lines:
                if line.strip().startswith('#') or not line.strip():
                    new_env.append(line)
                elif '=' in line:
                    key, value = line.split('=', 1)
                    value = current_vars.get(key, value)
                    new_env.append(f"{key}={value}")

            with open(env_file_dst, 'w') as file:
                file.write("\n".join(new_env))
            step("Файл .env успешно обновлен", "success")
            return True
        except Exception as e:
            step(f"Ошибка обновления .env файла: {e}", "error")
            return False

class FileManager:
    @staticmethod
    def check_location():
        required_dirs = [SCRIPT_CONFIGS_DIR, SCRIPT_IMAGES_DIR]
        missing_dirs = [d for d in required_dirs if not os.path.isdir(d)]
        if missing_dirs:
            step(f"Отсутствуют директории: {', '.join(missing_dirs)}", "error")
            logging.error(f"Запуск вне дистрибутива")
            return False
        step("Найдены все необходимые директории", "success")
        return True

    @staticmethod
    def create_directories():
        for dir_path in BASE_DIRS:
            try:
                os.makedirs(dir_path, exist_ok=True)
                step(f"Создана директория: {dir_path}", "success")
            except Exception as e:
                step(f"Ошибка создания директории {dir_path}: {e}", "error")

    @staticmethod
    def backup_files_and_images():
        bkp_day = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        backup_dir = os.path.join(BACKUP_DIR, f'backup_{bkp_day}')
        os.makedirs(backup_dir, exist_ok=True)
        step(f"Создана директория резервного копирования: {backup_dir}", "success")

        files_to_copy = [DOCKER_COMPOSE_DST, ENV_FILE_DST]
        files_to_move = [os.path.join(SCRIPT_IMAGES_DIR, f"{image}_bkp") for image in DOCKER_IMAGES]

        for file_path in files_to_move:
            if os.path.exists(file_path):
                dest_path = os.path.join(backup_dir, os.path.basename(file_path))
                if os.path.exists(dest_path):
                    os.remove(dest_path)
                try:
                    shutil.move(file_path, backup_dir)
                    step(f"Образ перемещен в бэкап: {file_path}", "success")
                except Exception as e:
                    step(f"Ошибка перемещения {file_path}: {e}", "error")
            else:
                step(f"Файл для перемещения не найден: {file_path}", "warning")

        for src_file in files_to_copy:
            try:
                shutil.copy2(src_file, backup_dir)
                step(f"Файл скопирован в бэкап: {src_file}", "success")
            except FileNotFoundError:
                step(f"Файл не найден: {src_file}", "error")

class DockerManager:
    @staticmethod
    def remove_old_containers_and_images():
        for container in CONTAINERS:
            try:
                subprocess.run(['docker', 'rm', '-f', container], check=True)
                step(f"Контейнер удален: {container}", "success")
            except subprocess.CalledProcessError as e:
                step(f"Ошибка удаления контейнера {container}: {e}", "error")

        for image in DOCKER_IMAGES:
            try:
                subprocess.run(['docker', 'rmi', '-f', image], check=True)
                step(f"Образ удален: {image}", "success")
            except subprocess.CalledProcessError as e:
                step(f"Ошибка удаления образа {image}: {e}", "error")
                
    @staticmethod
    def upload_downloaded_images():
        for image in DOCKER_IMAGES:
            image_path = os.path.join(SCRIPT_IMAGES_DIR, image)
            if os.path.exists(image_path):
                try:
                    subprocess.run(['docker', 'load', '-i', image_path], check=True)
                    step(f"Образ загружен: {image}", "success")
                except subprocess.CalledProcessError as e:
                    step(f"Ошибка загрузки образа {image}: {e}", "error")
            else:
                step(f"Образ не найден: {image_path}", "warning")
    @staticmethod
    def save_images():
        for image in DOCKER_IMAGES:
            bkp_file = os.path.join(SCRIPT_IMAGES_DIR, f"{image}_bkp")
            try:
                subprocess.run(f'docker save {image} -o {bkp_file}', shell=True, check=True)
                step(f"Образ сохранён: {bkp_file}", "success")
            except subprocess.CalledProcessError as e:
                step(f"Ошибка при сохранении образа {image}: {e}", "error")

class ContainerManager:
    @staticmethod
    def start_containers():
        try:
            subprocess.run(['sudo docker compose up -d'], shell=True, cwd=APP_DIR, check=True)
            step("Контейнеры запущены", "success")
        except subprocess.CalledProcessError as e:
            step(f"Ошибка запуска контейнеров: {e}", "error")

    @staticmethod
    def stop_containers():
        try:
            subprocess.run(['docker compose stop'], shell=True, cwd=APP_DIR, check=True)
            step("Контейнеры остановлены", "success")
        except subprocess.CalledProcessError as e:
            step(f"Ошибка остановки контейнеров: {e}", "error")

def main():
    if not check_docker_installed():
        return

    if not FileManager.check_location():
        return

    new_version = get_new_version()
    current_version = get_installed_version()

    print(Fore.MAGENTA + f"\n====== УСТАНОВКА/ОБНОВЛЕНИЕ MINT_TURN ======\nНовая версия: {new_version}  Установленная: {current_version}\n" + Style.RESET_ALL)
    logging.info(f"Версия ПО: {new_version}")

    print("Выберите действие:")
    print("1. Установить")
    print("2. Обновить")
    print("3. Выход")
    choice = input("Введите номер действия: ").strip()

    if choice == '1':
        step("Начата установка mint_turn", "progress")
        EnvironmentSetup.set_timezone()
        FileManager.create_directories()
        step("Начата загрузка образов", "progress")
        DockerManager.upload_downloaded_images()
        step("Начато копирование конфиг файлов", "progress")
        shutil.copy(DOCKER_COMPOSE_SRC, DOCKER_COMPOSE_DST)
        shutil.copy(ENV_FILE_SRC, ENV_FILE_DST)
        shutil.copy(VERSION_FILE_SRC, VERSION_FILE_DST)
        step("Установка mint_turn завершена", "success")
        setup_logrotate()

    elif choice == '2':
        step("Начато обновление mint_turn", "progress")
        ContainerManager.start_containers()
        step("Идет бекап текущих образов", "progress")
        DockerManager.save_images()
        FileManager.backup_files_and_images()
        step("Идет остановка контейнеров", "progress")
        ContainerManager.stop_containers()
        step("Начато удаление текущих образов", "progress")
        DockerManager.remove_old_containers_and_images()
        step("Начата загрузка новых образов", "progress")
        DockerManager.upload_downloaded_images()
        EnvironmentSetup.update_env_file(ENV_FILE_DST, ENV_FILE_SRC)
        shutil.copy(DOCKER_COMPOSE_SRC, DOCKER_COMPOSE_DST)
        ContainerManager.start_containers()
        shutil.copy(VERSION_FILE_SRC, VERSION_FILE_DST)
        step("Обновление mint_turn завершено", "success")
        setup_logrotate()

    elif choice == '3':
        step("Выход из программы", "warning")
    else:
        step("Неверный ввод. Пожалуйста, выберите 1, 2 или 3.", "error")

if __name__ == '__main__':
    main()
