#!/usr/bin/python3

import logging
import signal
import subprocess
import sys
import time
import threading
import os
from datetime import datetime, timedelta
from random import shuffle

from school_ringer_modules.daemon_scheduler import DaemonScheduler
from school_ringer_modules.process_runner import ProcessRunner
from school_ringer_modules.json_storage import JsonStorage
from school_ringer_modules.config_reader import ConfigReader
from school_ringer_modules.system import get_current_user_on_x, run_command, is_disabled_until_midnight
from school_ringer_modules.config import config_path, next_call_file

# Настройка логирования
for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)
logging.basicConfig(
    format=u'%(asctime)s %(filename)s [LINE:%(lineno)d] [%(funcName)s()] #%(levelname)-15s %(message)s',
    level=logging.INFO,
)

if is_disabled_until_midnight()[0]:
    logging.info('Сервис отключён до конца дня. Завершаем работу.')
    sys.exit(0)

# Инициализируем зависимости
process_runner = ProcessRunner()
config_reader = ConfigReader(process_runner)
schedules_storage = JsonStorage(f'{config_path}/schedule.json', process_runner)
groups_storage = JsonStorage(f'{config_path}/groups.json', process_runner)

# Создаём планировщик
scheduler = DaemonScheduler(process_runner, schedules_storage, groups_storage)

if os.path.exists(next_call_file):
    try:
        os.remove(next_call_file)
        logging.info('Удалён старый next_call_file.txt для корректного обновления')
    except Exception as e:
        logging.warning(f'Не удалось удалить next_call_file.txt: {e}')

# Инициализация
all_calls = scheduler.update_all_calls()
all_jobs = scheduler.schedule_all_calls(all_calls)
time.sleep(1)
scheduler.update_next_call_file()


def retry_update():
    scheduler.update_next_call_file()
    logging.info("Повторное обновление next_call_file")


threading.Timer(2.0, retry_update).start()

if all_jobs:
    next_call = all_jobs[0].next_run if all_jobs[0].next_run else None
    if next_call:
        logging.info(f'First task of daemon: {next_call}')

# ============ МУЗЫКА ПО ИНТЕРВАЛУ ============
music_interval_active = False
current_music_process = None


def get_seconds_until_next_minute():
    """
    Возвращает количество секунд до следующей полной минуты.
    Используется для синхронизации цикла музыки с началом минуты.
    @return: количество секунд (float)
    """
    now = datetime.now()
    seconds_until = 60 - now.second
    if seconds_until == 60:
        seconds_until = 0
    microseconds_until = 1_000_000 - now.microsecond
    return seconds_until + (microseconds_until / 1_000_000)


def is_time_in_interval(start_time_str, end_time_str):
    """
    Проверяет, находится ли текущее время в заданном интервале.
    @param start_time_str: время начала в формате HH:MM
    @param end_time_str: время окончания в формате HH:MM
    @return: True, если текущее время внутри интервала (включая начало, исключая конец)
    """
    try:
        now = datetime.now()
        current_seconds = now.hour * 3600 + now.minute * 60 + now.second
        start_hour, start_min = map(int, start_time_str.split(':'))
        end_hour, end_min = map(int, end_time_str.split(':'))
        start_seconds = start_hour * 3600 + start_min * 60
        end_seconds = end_hour * 3600 + end_min * 60
        return start_seconds <= current_seconds < end_seconds
    except Exception:
        return False


def stop_interval_music():
    """
    Останавливает воспроизведение музыки по интервалу.
    Убивает все процессы mpv, принадлежащие пользователю на X или school-ringer.
    """
    global music_interval_active, current_music_process
    logging.info('Останавливаем музыку по интервалу...')
    music_interval_active = False

    sound_user = get_current_user_on_x()
    if not sound_user:
        sound_user = 'school-ringer'

    run_command(f'sudo -u {sound_user} pkill -9 mpv')
    run_command('pkill -9 mpv')

    if current_music_process is not None:
        try:
            current_music_process.kill()
            current_music_process.wait(timeout=2)
        except:
            pass
        current_music_process = None

    logging.info('Музыка по интервалу остановлена')


def play_interval_music():
    """
    Запускает воспроизведение музыки по интервалу.
    Собирает плейлист из указанной папки, применяет настройки перемешивания и громкости,
    запускает в отдельном потоке последовательное воспроизведение треков.
    """
    global music_interval_active, current_music_process

    sound_user = get_current_user_on_x()
    if not sound_user:
        sound_user = 'school-ringer'

    logging.info(f'Музыка по интервалу от пользователя: {sound_user}')

    music_folder = run_command(
        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_music_folder'
    ).strip().strip('"')

    if not music_folder or music_folder == 'None' or not os.path.exists(music_folder):
        logging.error(f'Папка с музыкой не указана или не существует: {music_folder}')
        return

    playlist = []
    for file in os.listdir(music_folder):
        if file.split('.')[-1].lower() in ('mp3', 'wav', 'ogg'):
            playlist.append(os.path.join(music_folder, file))

    if not playlist:
        logging.warning('Нет музыкальных файлов')
        return

    music_shuffle = run_command(
        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_shuffle', log=False
    ).strip().lower() == 'true'
    if music_shuffle:
        shuffle(playlist)
        logging.info(f'Плейлист перемешан, {len(playlist)} файлов')
    else:
        logging.info(f'Найдено {len(playlist)} файлов')

    music_volume = run_command(
        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_volume', log=False
    ).strip()
    if not music_volume or music_volume == 'None':
        music_volume = '100'

    logging.info(f'Громкость: {music_volume}%')

    def play_task():
        """
        Внутренняя функция-задача для потока воспроизведения музыки.
        Последовательно проигрывает треки из плейлиста, проверяя перед каждым треком,
        активен ли интервал и не вышло ли время.
        """
        logging.info(f'play_task: sound_user={sound_user}, music_volume={music_volume}, playlist_len={len(playlist)}')
        global music_interval_active, current_music_process
        logging.info('play_task: НАЧАЛО работы потока')

        def kill_current_music():
            global current_music_process
            if current_music_process is not None:
                try:
                    if current_music_process.poll() is None:
                        logging.info(f'kill_current_music: убиваем процесс {current_music_process.pid}')
                        # Убиваем всю группу процессов (но только ту, что создали)
                        os.killpg(os.getpgid(current_music_process.pid), signal.SIGTERM)
                        time.sleep(0.5)
                        if current_music_process.poll() is None:
                            os.killpg(os.getpgid(current_music_process.pid), signal.SIGKILL)
                        current_music_process = None
                except Exception as e:
                    logging.warning(f'Ошибка остановки музыки по интервалу: {e}')
                    current_music_process = None

        def get_seconds_until_interval_end(end_time_str):
            """Возвращает количество секунд до окончания интервала"""
            from datetime import datetime, timedelta
            now = datetime.now()
            try:
                end_hour, end_min = map(int, end_time_str.split(':'))
                end_time = now.replace(hour=end_hour, minute=end_min, second=0, microsecond=0)
                if end_time < now:
                    end_time = end_time + timedelta(days=1)
                return (end_time - now).total_seconds()
            except:
                return 0

        try:
            for idx, track in enumerate(playlist):
                if not music_interval_active:
                    logging.info('play_task: флаг сброшен, выход')
                    kill_current_music()
                    break

                # Эти вызовы не пишем в лог — они вызываются часто
                interval_enabled = run_command(
                    f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_enabled',
                    log=False
                ).strip().lower() == 'true'
                if not interval_enabled:
                    logging.info('play_task: интервал отключён')
                    music_interval_active = False
                    kill_current_music()
                    break

                start_t = run_command(
                    f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_start',
                    log=False
                ).strip()
                end_t = run_command(
                    f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_end',
                    log=False
                ).strip()

                if not is_time_in_interval(start_t, end_t):
                    logging.info('play_task: ВРЕМЯ ВЫШЛО ИЗ ИНТЕРВАЛА!')
                    music_interval_active = False
                    kill_current_music()
                    break

                # Проверяем, достаточно ли времени в интервале для трека
                remaining = get_seconds_until_interval_end(end_t)
                if remaining < 1:
                    logging.info('play_task: интервал заканчивается, пропускаем трек')
                    music_interval_active = False
                    kill_current_music()
                    break

                script_content = f'''#!/bin/bash
        export DISPLAY=:0
        export XDG_RUNTIME_DIR=/run/user/$(id -u {sound_user})
        mpv --no-terminal --no-video --really-quiet --volume={music_volume} "{track}"
        '''
                script_path = f'/tmp/music_play_{os.getpid()}_{idx}.sh'
                with open(script_path, 'w') as f:
                    f.write(script_content)
                os.chmod(script_path, 0o755)

                cmd = ['sudo', '-u', sound_user, 'sh', script_path]
                logging.info(f'play_task: запуск трека {idx + 1}/{len(playlist)}: {os.path.basename(track)}')

                current_music_process = subprocess.Popen(cmd, start_new_session=True)

                start_time = time.time()

                while current_music_process.poll() is None:
                    time.sleep(1)

                    # Проверяем, не закончился ли интервал
                    if time.time() - start_time > remaining:
                        logging.info('play_task: интервал закончился, убиваем процесс')
                        kill_current_music()
                        music_interval_active = False
                        break

                    if not music_interval_active:
                        logging.info('play_task: получен сигнал остановки')
                        kill_current_music()
                        break

                    # Эти вызовы тоже не пишем в лог
                    interval_enabled = run_command(
                        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_enabled',
                        log=False
                    ).strip().lower() == 'true'
                    if not interval_enabled:
                        logging.info('play_task: интервал отключён во время трека')
                        music_interval_active = False
                        kill_current_music()
                        break

                    start_t = run_command(
                        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_start',
                        log=False
                    ).strip()
                    end_t = run_command(
                        f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_end',
                        log=False
                    ).strip()
                    if not is_time_in_interval(start_t, end_t):
                        logging.info('play_task: время вышло из интервала во время трека')
                        music_interval_active = False
                        kill_current_music()
                        break

                try:
                    os.remove(script_path)
                except:
                    pass

        except Exception as e:
            logging.error(f'play_task: ОШИБКА {e}', exc_info=True)
        finally:
            kill_current_music()
            music_interval_active = False
            logging.info('play_task: ЗАВЕРШЕНИЕ потока')

    music_interval_active = True
    music_thread = threading.Thread(target=play_task, daemon=True)
    music_thread.start()
    logging.info('Поток музыки по интервалу запущен')


def check_music_interval():
    """
    Проверяет состояние музыки по интервалу и управляет воспроизведением.
    Читает настройки интервала из конфига, определяет, нужно ли запустить или остановить музыку.
    """
    global music_interval_active

    try:
        interval_enabled = run_command(
            f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_enabled',
            log=False
        ).strip().lower() == 'true'

        if not interval_enabled:
            if music_interval_active:
                logging.info('Функция отключена, останавливаем музыку')
                stop_interval_music()
            return

        start_time = run_command(
            f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_start',
            log=False
        ).strip()
        end_time = run_command(
            f'py-ini-config get {config_path}/school-ringer.conf Main custom_music_interval_end',
            log=False
        ).strip()

        if not start_time or start_time == 'None' or not end_time or end_time == 'None':
            if music_interval_active:
                stop_interval_music()
            return

        should_play = is_time_in_interval(start_time, end_time)

        if should_play and not music_interval_active:
            logging.info(f'Запуск музыки по интервалу ({start_time}-{end_time})')
            play_interval_music()
        elif not should_play and music_interval_active:
            logging.info(f'Остановка музыки (интервал {start_time}-{end_time} закончился)')
            stop_interval_music()

    except Exception as e:
        logging.error(f'Ошибка проверки музыки: {e}')


def music_sync_loop():
    """
    Основной цикл синхронизации музыки по интервалу.
    Ждёт начала следующей минуты, затем каждую минуту проверяет состояние интервала.
    """
    wait_seconds = get_seconds_until_next_minute()
    if 0 < wait_seconds < 60:
        time.sleep(wait_seconds)

    while True:
        try:
            check_music_interval()
            time.sleep(60)
        except Exception as e:
            logging.error(f'Ошибка в цикле музыки: {e}')
            time.sleep(10)


# Запускаем поток музыки по интервалу
music_thread = threading.Thread(target=music_sync_loop, daemon=True)
music_thread.start()
logging.info('Модуль музыки по интервалу запущен')

import schedule


# ============ ОСНОВНОЙ ЦИКЛ ЗВОНКОВ ============
def is_disabled_until_midnight():
    """
    Проверяет, отключён ли сервис до конца дня.
    @return: True, если сервис отключён до конца дня
    """
    from school_ringer_modules.system import is_disabled_until_midnight as _is_disabled
    return _is_disabled()[0]


if os.path.exists('/etc/systemd/system/school-ringer-resume.timer'):
    subprocess.run('systemctl disable school-ringer-resume.timer --now', shell=True)
    logging.info('Таймер возобновления отключён')

if is_disabled_until_midnight():
    logging.info('Сервис отключён до конца дня. Ожидаем завтрашнего дня...')
    while is_disabled_until_midnight():
        time.sleep(60)
    logging.info('Новый день, включаем звонки...')
    all_calls = scheduler.update_all_calls()
    scheduler.schedule_all_calls(all_calls)
    scheduler.update_next_call_file()

while True:
    """
    Основной цикл демона.
    Выполняет запланированные звонки, обновляет файл следующего звонка,
    обрабатывает состояние отключения до конца дня.
    """
    if is_disabled_until_midnight():
        scheduler.update_next_call_file()
        time.sleep(60)
        continue

    sleeping_time = schedule.idle_seconds()
    if sleeping_time is not None and sleeping_time > 0:
        next_call = schedule.next_run()
        logging.info(f'До следующего звонка осталось {sleeping_time} секунд')
        logging.info(f'Следующий звонок: {next_call}')
        time.sleep(sleeping_time)
        schedule.run_pending()
        scheduler.update_next_call_file()
    else:
        # logging.info('Следующий звонок: -')
        time.sleep(5)
        scheduler.update_next_call_file()
