import asyncio
import re
from datetime import UTC, datetime, timedelta

import aiohttp
import structlog
from packaging import version

from app.config import settings


logger = structlog.get_logger(__name__)


class VersionInfo:
    def __init__(self, tag_name: str, published_at: str, name: str, body: str, prerelease: bool = False):
        self.tag_name = tag_name
        self.published_at = datetime.fromisoformat(published_at.replace('Z', '+00:00'))
        self.name = name or tag_name
        self.body = body
        self.prerelease = prerelease
        self.is_dev = 'dev' in tag_name.lower()

    @property
    def clean_version(self) -> str:
        return re.sub(r'^v', '', self.tag_name)

    @property
    def version_obj(self):
        try:
            clean_ver = self.clean_version
            if 'dev' in clean_ver:
                base_ver = clean_ver.split('-dev')[0]
                return version.parse(f'{base_ver}.dev')
            return version.parse(clean_ver)
        except Exception:
            return version.parse('0.0.0')

    @property
    def formatted_date(self) -> str:
        return self.published_at.strftime('%d.%m.%Y %H:%M')

    @property
    def short_description(self) -> str:
        if not self.body:
            return 'Без описания'

        description = self.body.strip()
        if len(description) > 350:
            description = description[:347] + '...'

        return description

    @property
    def full_description(self) -> str:
        """Full release body text, stripped of leading/trailing whitespace."""
        if not self.body:
            return ''
        return self.body.strip()


class VersionService:
    def __init__(self, bot=None):
        self.bot = bot
        self.repo = getattr(settings, 'VERSION_CHECK_REPO', 'fr1ngg/remnawave-bedolaga-telegram-bot')
        self.enabled = getattr(settings, 'VERSION_CHECK_ENABLED', True)
        self.current_version = self._get_current_version()
        self.cache_ttl = 3600
        self._cache: dict = {}
        self._last_check: datetime | None = None
        self._notification_service = None

    async def get_latest_stable_version(self) -> str:
        try:
            url = f'https://api.github.com/repos/{self.repo}/releases/latest'
            async with aiohttp.ClientSession() as session, session.get(url) as response:
                if response.status == 200:
                    data = await response.json()
                    return data['tag_name']
        except Exception:
            pass
        return 'UNKNOW'

    def _get_current_version(self) -> str:
        try:
            from pathlib import Path

            pyproject_path = Path(__file__).resolve().parents[2] / 'pyproject.toml'
            if pyproject_path.exists():
                for line in pyproject_path.read_text().splitlines():
                    if line.strip().startswith('version'):
                        ver = line.split('=', 1)[1].strip().strip('"').strip("'")
                        if ver:
                            return ver
        except Exception:
            pass

        return 'UNKNOW'

    def set_notification_service(self, notification_service):
        self._notification_service = notification_service

    async def check_for_updates(self, force: bool = False) -> tuple[bool, list[VersionInfo]]:
        if not self.enabled:
            return False, []

        try:
            releases = await self._fetch_releases(force)
            if not releases:
                return False, []

            current_ver = self._parse_version(self.current_version)
            newer_releases = []

            for release in releases:
                release_ver = release.version_obj
                if release_ver > current_ver:
                    newer_releases.append(release)

            newer_releases.sort(key=lambda x: x.version_obj, reverse=True)

            has_updates = len(newer_releases) > 0

            if has_updates and not force:
                await self._send_update_notification(newer_releases)

            return has_updates, newer_releases

        except Exception as e:
            logger.error('Ошибка проверки обновлений', error=e)
            return False, []

    async def _fetch_releases(self, force: bool = False) -> list[VersionInfo]:
        if not force and self._cache and self._last_check:
            if datetime.now(UTC) - self._last_check < timedelta(seconds=self.cache_ttl):
                return self._cache.get('releases', [])

        url = f'https://api.github.com/repos/{self.repo}/releases'

        try:
            timeout = aiohttp.ClientTimeout(total=10)
            async with aiohttp.ClientSession(timeout=timeout) as session, session.get(url) as response:
                if response.status == 200:
                    data = await response.json()
                    releases = []

                    for release_data in data[:20]:
                        release = VersionInfo(
                            tag_name=release_data['tag_name'],
                            published_at=release_data['published_at'],
                            name=release_data['name'],
                            body=release_data['body'] or '',
                            prerelease=release_data['prerelease'],
                        )
                        releases.append(release)

                    self._cache['releases'] = releases
                    self._last_check = datetime.now(UTC)

                    logger.info('Получено релизов из GitHub', releases_count=len(releases))
                    return releases
                logger.warning('GitHub API вернул статус', status=response.status)
                return []

        except TimeoutError:
            logger.warning('Таймаут при запросе к GitHub API')
            return []
        except Exception as e:
            logger.error('Ошибка запроса к GitHub API', error=e)
            return []

    def _parse_version(self, version_str: str):
        try:
            clean_ver = re.sub(r'^v', '', version_str)
            if 'dev' in clean_ver:
                base_ver = clean_ver.split('-dev')[0]
                return version.parse(f'{base_ver}.dev')
            if 'unknow' in clean_ver.lower():
                return version.parse('0.0.0')
            return version.parse(clean_ver)
        except Exception:
            return version.parse('0.0.0')

    async def _send_update_notification(self, newer_releases: list[VersionInfo]):
        if not self._notification_service or not newer_releases:
            return

        try:
            latest_version = newer_releases[0]
            cache_key = f'notified_{latest_version.tag_name}'

            if self._cache.get(cache_key):
                return

            await self._notification_service.send_version_update_notification(
                current_version=self.current_version, latest_version=latest_version, total_updates=len(newer_releases)
            )

            self._cache[cache_key] = True

        except Exception as e:
            logger.error('Ошибка отправки уведомления об обновлении', error=e)

    async def get_version_info(self) -> dict:
        try:
            has_updates, newer_releases = await self.check_for_updates()
            all_releases = await self._fetch_releases()

            current_release = None
            current_ver = self._parse_version(self.current_version)

            for release in all_releases:
                if release.version_obj == current_ver:
                    current_release = release
                    break

            return {
                'current_version': self.current_version,
                'current_release': current_release,
                'has_updates': has_updates,
                'newer_releases': newer_releases[:5],
                'total_newer': len(newer_releases),
                'last_check': self._last_check,
                'repo_url': f'https://github.com/{self.repo}',
            }

        except Exception as e:
            logger.error('Ошибка получения информации о версиях', error=e)
            return {
                'current_version': self.current_version,
                'current_release': None,
                'has_updates': False,
                'newer_releases': [],
                'total_newer': 0,
                'last_check': None,
                'repo_url': f'https://github.com/{self.repo}',
                'error': str(e),
            }

    async def start_periodic_check(self):
        if not self.enabled:
            logger.info('Проверка версий отключена')
            return

        logger.info('Запуск периодической проверки обновлений для', repo=self.repo)
        logger.info('Текущая версия', current_version=self.current_version)

        while True:
            try:
                await asyncio.sleep(3600)
                await self.check_for_updates()

            except asyncio.CancelledError:
                logger.info('Остановка проверки обновлений')
                break
            except Exception as e:
                logger.error('Ошибка в периодической проверке обновлений', error=e)
                await asyncio.sleep(300)

    def format_version_display(self, version_info: VersionInfo) -> str:
        status_icon = ''
        if version_info.prerelease:
            status_icon = '🧪'
        elif version_info.is_dev:
            status_icon = '🔧'
        else:
            status_icon = '📦'

        return f'{status_icon} {version_info.tag_name}'


version_service = VersionService()
