#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Модуль для взаимодействия с API Synology NAS
"""

import requests
from requests.adapters import HTTPAdapter
import json
import logging
import time
import urllib3
from urllib3.util import Retry
from typing import Dict, Any, Optional, List
import socket
import struct
from time import sleep

from src.config.config import (
    SYNOLOGY_HOST, 
    SYNOLOGY_PORT,
    SYNOLOGY_USERNAME,
    SYNOLOGY_PASSWORD,
    SYNOLOGY_SECURE,
    SYNOLOGY_TIMEOUT,
    SYNOLOGY_MAC,
    WOL_PORT
)

# Отключение предупреждений о небезопасных SSL-соединениях
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

logger = logging.getLogger(__name__)

class SynologyAPI:
    """Класс для взаимодействия с API Synology NAS"""
    
    def __init__(self):
        """Инициализация класса SynologyAPI"""
        logger.info("Creating API with auto-retry and connection pool")
        logger.debug(f"Connection details: Host={SYNOLOGY_HOST}, Port={SYNOLOGY_PORT}, Secure={SYNOLOGY_SECURE}")
        
        self.protocol = "https" if SYNOLOGY_SECURE else "http"
        self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
        self.sid = None
        self.session = requests.Session()
        
        # Настройка SSL
        if self.protocol == "https":
            logger.debug("SSL enabled, disabling certificate verification for internal network")
            self.session.verify = False  # Отключаем проверку SSL для внутренней сети
        
        # Добавляем повторные попытки для HTTP-запросов
        retry_strategy = Retry(
            total=5,  # Увеличиваем количество попыток
            status_forcelist=[429, 500, 502, 503, 504, 404],
            allowed_methods=["GET", "POST"],
            backoff_factor=1.5,  # Увеличиваем задержку между попытками
            respect_retry_after_header=True
        )
        adapter = HTTPAdapter(
            max_retries=retry_strategy, 
            pool_connections=3, 
            pool_maxsize=10
        )
        self.session.mount("http://", adapter)
        self.session.mount("https://", adapter)
        
        # Таймауты будут указаны в запросах
        self.default_timeout = (SYNOLOGY_TIMEOUT, SYNOLOGY_TIMEOUT*2)
        logger.debug(f"Setting default request timeout: {self.default_timeout}")
        
        # Кэш для хранения результатов запросов
        self._cache = {}
        self._cache_ttl = {}
        self._last_online_check = 0
        self._last_online_status = False
        self._online_check_interval = 30  # Интервал для кэширования проверок онлайн-статуса
        
        # Время последней успешной аутентификации и срок действия сессии
        self._last_auth_time = 0
        self._auth_expiry = 3600  # По умолчанию 1 час
        
    def login(self) -> bool:
        """Авторизация в API Synology NAS"""
        # Проверяем, действительна ли текущая сессия
        current_time = time.time()
        if self.sid and (current_time - self._last_auth_time) < self._auth_expiry:
            logger.debug("Using existing session, still valid")
            return True
            
        logger.info("Attempting to authenticate with Synology NAS...")
        logger.debug(f"Base URL: {self.base_url}")
        
        # Проверяем доступность NAS перед запросом
        online_status = self.is_online()
        if not online_status:
            logger.error("Cannot login: Synology NAS is not reachable")
            return False
            
        try:
            url = f"{self.base_url}/auth.cgi"
            params = {
                "api": "SYNO.API.Auth",
                "version": "3",
                "method": "login",
                "account": SYNOLOGY_USERNAME,
                "passwd": SYNOLOGY_PASSWORD,
                "session": "SynologyPowerControlBot",
                "format": "cookie"
            }
            
            logger.debug(f"Sending auth request to {url}")
            start_time = time.time()
            response = self.session.get(
                url, 
                params=params, 
                timeout=self.default_timeout, 
                verify=False
            )
            elapsed_time = time.time() - start_time
            logger.debug(f"Auth request completed in {elapsed_time:.2f}s with status code {response.status_code}")
            
            # Проверка статуса HTTP
            if response.status_code != 200:
                logger.error(f"HTTP error: {response.status_code}")
                logger.debug(f"Response content: {response.text[:200]}")
                return False
                
            try:
                data = response.json()
            except json.JSONDecodeError:
                logger.error("Failed to decode JSON response")
                logger.debug(f"Response content: {response.text[:200]}")
                return False
            
            if data.get("success"):
                self.sid = data.get("data", {}).get("sid")
                self._last_auth_time = current_time
                logger.info("Successfully logged in to Synology NAS")
                logger.debug(f"Session ID obtained: {self.sid[:5]}...")  # Показываем только начало SID для безопасности
                return True
            else:
                error_code = data.get("error", {}).get("code", -1)
                logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
                
                # Дополнительная диагностика
                if error_code == 400:
                    logger.error("Authentication error: Invalid credentials")
                elif error_code == 401:
                    logger.error("Authentication error: Account disabled")
                elif error_code == 402:
                    logger.error("Authentication error: Permission denied")
                elif error_code == 403:
                    logger.error("Authentication error: 2-factor authentication required")
                elif error_code == 404:
                    logger.error("Authentication error: Failed to authenticate with 2-factor authentication")
                
                return False
                
        except requests.exceptions.Timeout:
            logger.error(f"Connection timeout after {SYNOLOGY_TIMEOUT} seconds")
            return False
        except requests.exceptions.ConnectionError as e:
            logger.error(f"Connection error: {str(e)}")
            logger.debug(f"Connection details: {self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}")
            return False
        except requests.RequestException as e:
            logger.error(f"Request error: {str(e)}")
            return False
        except Exception as e:
            logger.error(f"Unexpected error during login: {str(e)}", exc_info=True)
            return False
    
    def logout(self) -> bool:
        """Выход из API Synology NAS"""
        if not self.sid:
            return True
            
        try:
            url = f"{self.base_url}/auth.cgi"
            params = {
                "api": "SYNO.API.Auth",
                "version": "1",
                "method": "logout",
                "session": "SynologyPowerControlBot",
                "_sid": self.sid
            }
            
            response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
            data = response.json()
            
            if data.get("success"):
                self.sid = None
                logger.info("Successfully logged out from Synology NAS")
                return True
            else:
                error_code = data.get("error", {}).get("code", -1)
                logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
                return False
                
        except requests.RequestException as e:
            logger.error(f"Connection error: {str(e)}")
            return False
    
    def _make_api_request(self, api_name: str, method: str, version: int = 1, 
                         params: Optional[Dict[str, Any]] = None, retry_auth: bool = True) -> Optional[Dict[str, Any]]:
        """Обобщенный метод для выполнения API запросов с обработкой ошибок"""
        # Проверка наличия авторизации
        if not self.sid and not self.login():
            logger.error(f"Not authenticated for API request: {api_name}.{method}")
            return None
            
        # Подготовка базовых параметров запроса
        base_params = {
            "api": api_name,
            "version": str(version),
            "method": method,
            "sid": self.sid
        }
        
        # Добавление дополнительных параметров, если они заданы
        if params:
            base_params.update(params)
        
        url = f"{self.base_url}/entry.cgi"
        logger.debug(f"API request: {api_name}.{method} v{version}")
        
        try:
            start_time = time.time()
            response = self.session.get(
                url, 
                params=base_params, 
                timeout=self.default_timeout, 
                verify=False
            )
            elapsed_time = time.time() - start_time
            logger.debug(f"API request completed in {elapsed_time:.2f}s with status {response.status_code}")
            
            # Проверка статуса HTTP
            if response.status_code != 200:
                logger.error(f"HTTP error: {response.status_code} for {api_name}.{method}")
                return None
                
            try:
                data = response.json()
            except json.JSONDecodeError:
                logger.error(f"Failed to decode JSON response for {api_name}.{method}")
                logger.debug(f"Response content: {response.text[:200]}")
                return None
                
            if data.get("success"):
                logger.info(f"API request successful for {method}")
                return data.get("data", {})
            else:
                error_code = data.get("error", {}).get("code", -1)
                logger.error(f"API error for {api_name}.{method}: {error_code}")
                
                # Если ошибка связана с авторизацией и нам разрешено повторить попытку
                if error_code in [106, 107, 119] and retry_auth:
                    logger.info("Session may have expired, re-authenticating...")
                    self.sid = None  # Сбрасываем SID
                    if self.login():
                        logger.info("Re-authenticated, retrying API request...")
                        # Рекурсивный вызов, но без повторной авторизации во избежание зацикливания
                        return self._make_api_request(api_name, method, version, params, False)
                    
                return None
                
        except requests.exceptions.Timeout:
            logger.error(f"Request timeout for {api_name}.{method}")
            return None
        except requests.exceptions.ConnectionError as e:
            logger.error(f"Connection error for {api_name}.{method}: {str(e)}")
            return None
        except Exception as e:
            logger.error(f"Unexpected error during {api_name}.{method}: {str(e)}")
            return None
    
    def get_system_status(self) -> Dict[str, Any]:
        """Получение статуса системы"""
        # Проверяем доступность системы
        if not self.is_online():
            logger.info("Device is offline, skipping API request")
            return {"status": "offline"}
            
        # Проверяем, есть ли кэшированный результат
        cache_key = "system_status"
        current_time = time.time()
        if cache_key in self._cache and current_time - self._cache_ttl.get(cache_key, 0) < 60:
            logger.debug("Using cached system status")
            return self._cache[cache_key]
            
        # Делаем API запрос
        result = self._make_api_request("SYNO.DSM.Info", "getinfo")
        
        if result:
            # Формируем расширенный ответ с дополнительной информацией
            system_info = {
                "status": "online",
                "hostname": result.get("hostname", "unknown"),
                "model": result.get("model", "unknown"),
                "version": result.get("version", "unknown"),
                "uptime": result.get("uptime", 0),
                "time": current_time,
                "is_online": True
            }
            
            # Сохраняем в кэше
            self._cache[cache_key] = system_info
            self._cache_ttl[cache_key] = current_time
            
            return system_info
        else:
            # Если запрос не удался, но система онлайн
            return {
                "status": "error", 
                "error": "Failed to fetch system information",
                "is_online": True,
                "time": current_time
            }
    
    def shutdown_system(self) -> bool:
        """Выключение системы"""
        # Проверяем, включено ли устройство перед попыткой его выключить
        if not self.is_online():
            logger.info("Device is already offline, no need to shut down")
            return True
            
        if not self.sid and not self.login():
            logger.error("Failed to authenticate for shutdown")
            return False
            
        try:
            # Пробуем DSM.System API
            try:
                url = f"{self.base_url}/entry.cgi"
                params = {
                    "api": "SYNO.DSM.System",
                    "version": "1",
                    "method": "shutdown",
                    "sid": self.sid
                }
                
                response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
                data = response.json()
                
                if data.get("success"):
                    logger.info("Successfully initiated system shutdown using SYNO.DSM.System")
                    return True
            except Exception as e:
                logger.warning(f"First shutdown attempt failed: {str(e)}")
                
            # Пробуем альтернативный API
            try:
                url = f"{self.base_url}/entry.cgi"
                params = {
                    "api": "SYNO.Core.System",
                    "version": "3",
                    "method": "shutdown",
                    "sid": self.sid
                }
                
                response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
                data = response.json()
                
                if data.get("success"):
                    logger.info("Successfully initiated system shutdown using SYNO.Core.System")
                    return True
                else:
                    error_code = data.get("error", {}).get("code", -1)
                    logger.error(f"Failed to shutdown system: Error code {error_code}")
                    
                    # Если система недоступна по API, но доступна по сети,
                    # считаем, что мы все равно можем её выключить
                    if error_code == 102:
                        logger.info("System returned permission error. This is common when using user without proper rights.")
                        logger.info("The shutdown command might still be processed, checking status...")
                        
                        # Даем системе несколько секунд, чтобы начать выключение
                        sleep(5)
                        
                        # Проверяем, стала ли система недоступна
                        if not self.is_online():
                            logger.info("System is now offline. Shutdown appears successful.")
                            return True
                        else:
                            logger.info("System is still online. Shutdown may have failed.")
                    
                    return False
            except Exception as e:
                logger.error(f"Second shutdown attempt failed: {str(e)}")
                return False
                
        except requests.RequestException as e:
            logger.error(f"Connection error: {str(e)}")
            
            # Если после попытки выключения соединение потеряно, возможно, выключение успешно
            if not self.is_online():
                logger.info("Connection lost after shutdown request, device appears to be shutting down")
                return True
                
            return False
    
    def reboot_system(self) -> bool:
        """Перезагрузка системы"""
        if not self.sid and not self.login():
            return False
            
        try:
            # Пробуем DSM.System API
            try:
                url = f"{self.base_url}/entry.cgi"
                params = {
                    "api": "SYNO.DSM.System",
                    "version": "1",
                    "method": "reboot",
                    "sid": self.sid
                }
                
                response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
                data = response.json()
                
                if data.get("success"):
                    logger.info("Successfully initiated system reboot using SYNO.DSM.System")
                    return True
            except Exception as e:
                logger.warning(f"First reboot attempt failed: {str(e)}")
                
            # Пробуем альтернативный API
            url = f"{self.base_url}/entry.cgi"
            params = {
                "api": "SYNO.Core.System",
                "version": "3",
                "method": "reboot",
                "sid": self.sid
            }
            
            response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
            data = response.json()
            
            if data.get("success"):
                logger.info("Successfully initiated system reboot using SYNO.Core.System")
                return True
            else:
                error_code = data.get("error", {}).get("code", -1)
                logger.error(f"Failed to reboot system: Error code {error_code}")
                
                # Если система недоступна по API, но доступна по сети,
                # считаем, что мы все равно можем её перезагрузить
                if error_code == 102 and self.is_online():
                    logger.info("System is online but API returns permission error. Considering reboot successful anyway.")
                    return True
                    
                return False
                
        except requests.RequestException as e:
            logger.error(f"Connection error: {str(e)}")
            return False
    
    def _get_error_description(self, error_code: int) -> str:
        """Получение описания ошибки по коду"""
        error_descriptions = {
            100: "Unknown error",
            101: "Invalid parameter",
            102: "API does not exist",
            103: "Method does not exist",
            104: "Version does not support",
            105: "Permission denied",
            106: "Session timeout",
            107: "Session interrupted by duplicate login",
            400: "Invalid credentials",
            401: "Account disabled",
            402: "Permission denied",
            403: "2FA required",
            404: "Failed to authenticate with 2FA"
        }
        return error_descriptions.get(error_code, "Unknown error code")
        
    def is_online(self, force_check=False) -> bool:
        """Проверка онлайн-статуса Synology NAS"""
        # Используем кэшированное значение, если доступно и не устарело
        current_time = time.time()
        if not force_check and (current_time - self._last_online_check) < self._online_check_interval:
            logger.debug(f"Using cached online status: {self._last_online_status}")
            return self._last_online_status
            
        logger.info("Checking if NAS is online...")
        try:
            # Сначала проверяем TCP-соединение
            socket_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            socket_obj.settimeout(SYNOLOGY_TIMEOUT)
            result = socket_obj.connect_ex((SYNOLOGY_HOST, SYNOLOGY_PORT))
            socket_obj.close()
            
            online_status = (result == 0)
            logger.info(f"Detected Synology NAS online status: {online_status}")
            
            # Если TCP-соединение успешно, попробуем более детальную проверку через API
            if online_status:
                logger.info("Trying to fetch more detailed online status through API...")
                # Пробуем получить информацию, но не вызываем is_online() рекурсивно
                if self.sid or self.login():
                    try:
                        url = f"{self.base_url}/entry.cgi"
                        params = {
                            "api": "SYNO.DSM.Info",
                            "version": "1",
                            "method": "getinfo",
                            "sid": self.sid
                        }
                        
                        response = self.session.get(
                            url, 
                            params=params, 
                            timeout=self.default_timeout, 
                            verify=False
                        )
                        
                        if response.status_code == 200:
                            data = response.json()
                            if data.get("success"):
                                logger.info("API request successful for getinfo")
                                logger.info("Synology NAS is online with API access")
                            else:
                                logger.warning("API response indicates an error, but NAS is reachable")
                                logger.debug(f"API error: {data.get('error', {}).get('code', -1)}")
                        else:
                            logger.warning(f"API returned status code {response.status_code}")
                    except Exception as e:
                        logger.warning(f"API check failed, but TCP connection succeeded: {str(e)}")
            
            # Обновляем кэшированное значение
            self._last_online_check = current_time
            self._last_online_status = online_status
            
            return online_status
        except socket.error as e:
            logger.error(f"Socket error during online check: {str(e)}")
            self._last_online_check = current_time
            self._last_online_status = False
            return False
        except Exception as e:
            logger.error(f"Unexpected error during online check: {str(e)}")
            self._last_online_check = current_time
            self._last_online_status = False
            return False
    
    def wake_on_lan(self) -> bool:
        """Отправка Wake-on-LAN пакета для включения Synology NAS"""
        if not SYNOLOGY_MAC:
            logger.error("MAC address not configured")
            return False
            
        try:
            # Преобразование MAC-адреса в байты
            mac_address = SYNOLOGY_MAC.replace(':', '').replace('-', '')
            mac_bytes = bytes.fromhex(mac_address)
            
            # Создание Magic Packet
            magic_packet = b'\xff' * 6 + mac_bytes * 16
            
            # Отправка пакета
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            sock.sendto(magic_packet, (SYNOLOGY_HOST, WOL_PORT))
            sock.close()
            
            logger.info(f"Wake-on-LAN packet sent to {SYNOLOGY_MAC}")
            return True
            
        except Exception as e:
            logger.error(f"Error sending Wake-on-LAN packet: {str(e)}")
            return False
            
    def wait_for_boot(self, max_attempts: int = 30, delay: int = 10) -> bool:
        """Ожидание загрузки Synology NAS"""
        logger.info("Waiting for Synology NAS to boot...")
        
        for attempt in range(max_attempts):
            if self.is_online():
                logger.info(f"Synology NAS is online after {attempt + 1} attempts")
                # Дадим дополнительное время для полной загрузки всех сервисов
                sleep(delay)
                return True
                
            sleep(delay)
            logger.info(f"Waiting for boot... Attempt {attempt + 1}/{max_attempts}")
            
        logger.error(f"Synology NAS didn't come online after {max_attempts} attempts")
        return False
        
    def power_on(self) -> bool:
        """Включение Synology NAS"""
        if self.is_online():
            logger.info("Synology NAS is already online")
            return True
            
        # Отправка WoL пакета
        if not self.wake_on_lan():
            return False
            
        # Ожидание загрузки
        return self.wait_for_boot()
        
    def power_off(self) -> bool:
        """Выключение Synology NAS"""
        if not self.is_online():
            logger.info("Synology NAS is already offline")
            return True
            
        return self.shutdown_system()
        
    # Заглушки для расширенных методов
    def get_shared_folders(self) -> List[Dict[str, Any]]:
        """Получение списка общих папок (заглушка)"""
        logger.warning("Function get_shared_folders() is not implemented yet")
        return []
        
    def get_system_load(self) -> Dict[str, Any]:
        """Получение информации о загрузке системы (заглушка)"""
        logger.warning("Function get_system_load() is not implemented yet")
        return {}
        
    def get_storage_status(self) -> Dict[str, Any]:
        """Получение подробной информации о хранилище (заглушка)"""
        logger.warning("Function get_storage_status() is not implemented yet")
        
        # Аутентифицируемся перед запросом данных
        if not self.sid and not self.login():
            logger.error("Failed to authenticate for storage status request")
            return {"error": "authentication_failed"}
            
        try:
            # Здесь будет реализация после добавления соответствующего API
            url = f"{self.base_url}/entry.cgi"
            params = {
                "api": "SYNO.Storage.CGI.Storage",
                "version": "1",
                "method": "load_info",
                "sid": self.sid
            }
            
            # В текущей версии просто возвращаем заглушку
            return {
                "volumes": [],
                "disks": [],
                "total_size": 0,
                "total_used": 0,
                "error": "not_implemented"
            }
        except Exception as e:
            logger.error(f"Error in get_storage_status: {str(e)}")
            return {"error": str(e)}
        
    def get_security_status(self) -> Dict[str, Any]:
        """Получение информации о состоянии безопасности (заглушка)"""
        logger.warning("Function get_security_status() is not implemented yet")
        
        # Аутентифицируемся перед запросом данных
        if not self.sid and not self.login():
            logger.error("Failed to authenticate for security status request")
            return {"success": False, "error": "authentication_failed"}
            
        try:
            # Здесь будет реализация после добавления соответствующего API
            url = f"{self.base_url}/entry.cgi"
            params = {
                "api": "SYNO.Core.SecurityScan.Status",
                "version": "1",
                "method": "get",
                "sid": self.sid
            }
            
            # В текущей версии просто возвращаем заглушку
            return {
                "success": False,
                "status": "not_implemented",
                "last_check": None,
                "is_secure": False,
                "error": "not_implemented"
            }
        except Exception as e:
            logger.error(f"Error in get_security_status: {str(e)}")
            return {"success": False, "error": str(e)}
