import asyncio
import base64
import html as html_mod
import re
import time
from datetime import datetime
from typing import Any
from urllib.parse import quote

import structlog
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup

from app.config import get_traffic_prices, settings
from app.database.models import Subscription, User
from app.localization.texts import get_texts
from app.utils.pricing_utils import (
    apply_percentage_discount,
    get_remaining_months,
)
from app.utils.promo_offer import (
    get_user_active_promo_discount_percent,
)


logger = structlog.get_logger(__name__)

TRAFFIC_PRICES = get_traffic_prices()

# ── App config cache ──
_app_config_cache: dict[str, Any] = {}
_app_config_cache_ts: float = 0.0
_app_config_lock = asyncio.Lock()


_PLACEHOLDER_RE = re.compile(r'\{(\w+)\}')


def _format_text_with_placeholders(template: str, values: dict[str, Any]) -> str:
    """Safe placeholder substitution — only replaces simple {key} patterns.

    Unlike str.format_map, this does NOT allow attribute access ({key.attr})
    or indexing ({key[0]}), preventing format string injection attacks.
    """
    if not isinstance(template, str):
        return template

    def _replace(match: re.Match) -> str:
        key = match.group(1)
        if key in values:
            return str(values[key])
        return match.group(0)

    try:
        return _PLACEHOLDER_RE.sub(_replace, template)
    except Exception:  # pragma: no cover - defensive logging
        logger.warning('Failed to format template with values', template=template, values=values)
        return template


def _get_addon_discount_percent_for_user(
    user: User | None,
    category: str,
    period_days_hint: int | None = None,
) -> int:
    if user is None:
        return 0

    promo_group = user.get_primary_promo_group()
    if promo_group is None:
        return 0

    if not getattr(promo_group, 'apply_discounts_to_addons', True):
        return 0

    try:
        return user.get_promo_discount(category, period_days_hint)
    except AttributeError:
        return 0


def _apply_addon_discount(
    user: User | None,
    category: str,
    amount: int,
    period_days_hint: int | None = None,
) -> dict[str, int]:
    percent = _get_addon_discount_percent_for_user(user, category, period_days_hint)
    discounted_amount, discount_value = apply_percentage_discount(amount, percent)

    return {
        'discounted': discounted_amount,
        'discount': discount_value,
        'percent': percent,
    }


def _get_promo_offer_discount_percent(user: User | None) -> int:
    return get_user_active_promo_discount_percent(user)


def _apply_promo_offer_discount(user: User | None, amount: int) -> dict[str, int]:
    percent = _get_promo_offer_discount_percent(user)

    if amount <= 0 or percent <= 0:
        return {'discounted': amount, 'discount': 0, 'percent': 0}

    discounted, discount_value = apply_percentage_discount(amount, percent)
    return {'discounted': discounted, 'discount': discount_value, 'percent': percent}


def _get_period_hint_from_subscription(subscription: Subscription | None) -> int | None:
    if not subscription:
        return None

    months_remaining = get_remaining_months(subscription.end_date)
    if months_remaining <= 0:
        return None

    return months_remaining * 30


def _apply_discount_to_monthly_component(
    amount_per_month: int,
    percent: int,
    months: int,
) -> dict[str, int]:
    discounted_per_month, discount_per_month = apply_percentage_discount(amount_per_month, percent)

    return {
        'original_per_month': amount_per_month,
        'discounted_per_month': discounted_per_month,
        'discount_percent': max(0, min(100, percent)),
        'discount_per_month': discount_per_month,
        'total': discounted_per_month * months,
        'discount_total': discount_per_month * months,
    }


def update_traffic_prices():
    from app.config import refresh_traffic_prices

    refresh_traffic_prices()
    logger.info('🔄 TRAFFIC_PRICES обновлены из конфигурации')


def format_traffic_display(traffic_gb: int, is_fixed_mode: bool = None) -> str:
    if is_fixed_mode is None:
        is_fixed_mode = settings.is_traffic_fixed()

    if traffic_gb == 0:
        if is_fixed_mode:
            return 'Безлимитный'
        return 'Безлимитный'
    if is_fixed_mode:
        return f'{traffic_gb} ГБ'
    return f'{traffic_gb} ГБ'


def validate_traffic_price(gb: int) -> bool:
    from app.config import settings

    price = settings.get_traffic_price(gb)
    if gb == 0:
        return True

    return price > 0


def get_localized_value(values: Any, language: str, default_language: str = 'en') -> str:
    if not isinstance(values, dict):
        return ''

    candidates: list[str] = []
    normalized_language = (language or '').strip().lower()

    if normalized_language:
        candidates.append(normalized_language)
        if '-' in normalized_language:
            candidates.append(normalized_language.split('-')[0])

    default_language = (default_language or '').strip().lower()
    if default_language and default_language not in candidates:
        candidates.append(default_language)

    for candidate in candidates:
        if not candidate:
            continue
        value = values.get(candidate)
        if isinstance(value, str) and value.strip():
            return value

    for value in values.values():
        if isinstance(value, str) and value.strip():
            return value

    return ''


def render_guide_blocks(blocks: list[dict], language: str) -> str:
    """Render block-format guide steps to HTML text."""
    parts: list[str] = []
    step_num = 1
    for block in blocks:
        if not isinstance(block, dict):
            continue
        title = block.get('title', {})
        desc = block.get('description', {})
        title_text = html_mod.escape(
            get_localized_value(title, language) if isinstance(title, dict) else str(title or '')
        )
        desc_text = html_mod.escape(get_localized_value(desc, language) if isinstance(desc, dict) else str(desc or ''))
        if title_text or desc_text:
            step = f'<b>Шаг {step_num}'
            if title_text:
                step += f' - {title_text}'
            step += ':</b>'
            if desc_text:
                step += f'\n{desc_text}'
            parts.append(step)
            step_num += 1
    return '\n\n'.join(parts)


def build_redirect_link(target_link: str | None, template: str | None) -> str | None:
    if not target_link or not template:
        return None

    normalized_target = str(target_link).strip()
    normalized_template = str(template).strip()

    if not normalized_target or not normalized_template:
        return None

    encoded_target = quote(normalized_target, safe='')
    result = normalized_template
    replaced = False

    replacements = [
        ('{subscription_link}', encoded_target),
        ('{link}', encoded_target),
        ('{subscription_link_raw}', normalized_target),
        ('{link_raw}', normalized_target),
    ]

    for placeholder, replacement in replacements:
        if placeholder in result:
            result = result.replace(placeholder, replacement)
            replaced = True

    if not replaced:
        result = f'{result}{encoded_target}'

    return result


def get_device_name(device_type: str, language: str = 'ru') -> str:
    names = {
        'ios': 'iPhone/iPad',
        'android': 'Android',
        'windows': 'Windows',
        'mac': 'macOS',
        'linux': 'Linux',
        'tv': 'Android TV',
        'appletv': 'Apple TV',
        'apple_tv': 'Apple TV',
    }

    return names.get(device_type, device_type)


# ── Remnawave async config loader ──

_PLATFORM_DISPLAY = {
    'ios': {'name': 'iPhone/iPad', 'emoji': '📱'},
    'android': {'name': 'Android', 'emoji': '🤖'},
    'windows': {'name': 'Windows', 'emoji': '💻'},
    'macos': {'name': 'macOS', 'emoji': '🎯'},
    'linux': {'name': 'Linux', 'emoji': '🐧'},
    'androidTV': {'name': 'Android TV', 'emoji': '📺'},
    'appleTV': {'name': 'Apple TV', 'emoji': '📺'},
}

# Map callback device_type keys to Remnawave platform keys
_DEVICE_TO_PLATFORM = {
    'ios': 'ios',
    'android': 'android',
    'windows': 'windows',
    'mac': 'macos',
    'linux': 'linux',
    'tv': 'androidTV',
    'appletv': 'appleTV',
    'apple_tv': 'appleTV',
}

# Reverse: Remnawave platform key → callback device_type
_PLATFORM_TO_DEVICE = {
    'ios': 'ios',
    'android': 'android',
    'windows': 'windows',
    'macos': 'mac',
    'linux': 'linux',
    'androidTV': 'tv',
    'appleTV': 'appletv',
}


def _get_remnawave_config_uuid() -> str | None:
    try:
        from app.services.system_settings_service import bot_configuration_service

        return bot_configuration_service.get_current_value('CABINET_REMNA_SUB_CONFIG')
    except Exception as e:
        logger.debug('Could not read CABINET_REMNA_SUB_CONFIG from service, using settings fallback', error=e)
        return getattr(settings, 'CABINET_REMNA_SUB_CONFIG', None)


async def load_app_config_async() -> dict[str, Any] | None:
    """Load app config from Remnawave API (if configured), with TTL cache.

    Returns None when no Remnawave config is set or API fails.
    """
    global _app_config_cache, _app_config_cache_ts

    ttl = settings.APP_CONFIG_CACHE_TTL
    if _app_config_cache and (time.monotonic() - _app_config_cache_ts) < ttl:
        return _app_config_cache

    async with _app_config_lock:
        # Double-check after acquiring lock
        if _app_config_cache and (time.monotonic() - _app_config_cache_ts) < ttl:
            return _app_config_cache

        remnawave_uuid = _get_remnawave_config_uuid()

        if remnawave_uuid:
            try:
                from app.services.remnawave_service import RemnaWaveService

                service = RemnaWaveService()
                async with service.get_api_client() as api:
                    config = await api.get_subscription_page_config(remnawave_uuid)
                    if config and config.config:
                        raw = dict(config.config)
                        raw['_isRemnawave'] = True
                        _app_config_cache = raw
                        _app_config_cache_ts = time.monotonic()
                        logger.debug('Loaded app config from Remnawave', remnawave_uuid=remnawave_uuid)
                        return raw
            except Exception as e:
                logger.warning('Failed to load Remnawave config', error=e)

        return None


def invalidate_app_config_cache() -> None:
    """Clear the cached app config so next call re-fetches from Remnawave.

    Note: This is intentionally sync (called from sync contexts in cabinet API).
    Setting timestamp to 0 first ensures the fast-path check in load_app_config_async
    fails immediately, even without acquiring _app_config_lock.
    """
    global _app_config_cache, _app_config_cache_ts
    _app_config_cache_ts = 0.0
    _app_config_cache = {}


async def get_apps_for_platform_async(device_type: str, language: str = 'ru') -> list[dict[str, Any]]:
    """Get apps for a device type from Remnawave config."""
    config = await load_app_config_async()
    if not config:
        return []

    platforms = config.get('platforms', {})
    if not isinstance(platforms, dict):
        return []

    platform_key = _DEVICE_TO_PLATFORM.get(device_type, device_type)
    platform_data = platforms.get(platform_key)
    if isinstance(platform_data, dict):
        apps = platform_data.get('apps', [])
        return [normalize_app(app) for app in apps if isinstance(app, dict)]
    return []


def normalize_app(app: dict[str, Any]) -> dict[str, Any]:
    """Normalize Remnawave app dict to a unified format with blocks."""
    return {
        'id': app.get('id', app.get('name', 'unknown')),
        'name': app.get('name', ''),
        'isFeatured': app.get('featured', app.get('isFeatured', False)),
        'urlScheme': app.get('urlScheme', ''),
        'isNeedBase64Encoding': app.get('isNeedBase64Encoding', False),
        'blocks': app.get('blocks', []),
        '_raw': app,
    }


def get_platforms_list(config: dict[str, Any]) -> list[dict[str, Any]]:
    """Extract available platforms from config for keyboard generation.

    Returns list of {key, displayName, icon_emoji, device_type} sorted by typical order.
    """
    platforms = config.get('platforms', {})
    if not isinstance(platforms, dict):
        return []

    # Desired order
    order = ['ios', 'android', 'windows', 'macos', 'linux', 'androidTV', 'appleTV']

    result = []
    for pk in order:
        if pk not in platforms:
            continue
        pd = platforms[pk]

        if not isinstance(pd, dict) or not pd.get('apps'):
            continue

        display = _PLATFORM_DISPLAY.get(pk, {'name': pk, 'emoji': '📱'})

        # Get displayName from Remnawave or fallback
        display_name_data = pd.get('displayName', display['name'])

        result.append(
            {
                'key': pk,
                'displayName': display_name_data,
                'icon_emoji': display['emoji'],
                'device_type': _PLATFORM_TO_DEVICE.get(pk, pk),
            }
        )

    # Also include any platforms in config not in our order list
    for pk, pd in platforms.items():
        if pk in order:
            continue
        if not isinstance(pd, dict) or not pd.get('apps'):
            continue

        display = _PLATFORM_DISPLAY.get(pk, {'name': pk, 'emoji': '📱'})
        result.append(
            {
                'key': pk,
                'displayName': display.get('name', pk),
                'icon_emoji': display.get('emoji', '📱'),
                'device_type': _PLATFORM_TO_DEVICE.get(pk, pk),
            }
        )

    return result


def resolve_button_url(
    url: str,
    subscription_url: str | None,
    crypto_link: str | None = None,
) -> str:
    """Resolve template variables in button URLs (port of cabinet's _resolve_button_url)."""
    if not url:
        return url
    result = url
    if subscription_url:
        result = result.replace('{{SUBSCRIPTION_LINK}}', subscription_url)
    if crypto_link:
        result = result.replace('{{HAPP_CRYPT3_LINK}}', crypto_link)
        result = result.replace('{{HAPP_CRYPT4_LINK}}', crypto_link)
    return result


def create_deep_link(app: dict[str, Any], subscription_url: str) -> str | None:
    if not subscription_url:
        return None

    if not isinstance(app, dict):
        return subscription_url

    scheme = str(app.get('urlScheme', '')).strip()
    payload = subscription_url

    if app.get('isNeedBase64Encoding'):
        try:
            payload = base64.b64encode(subscription_url.encode('utf-8')).decode('utf-8')
        except Exception as exc:
            logger.warning(
                'Не удалось закодировать ссылку подписки в base64 для приложения', app=app.get('id'), exc=exc
            )
            payload = subscription_url

    scheme_link = f'{scheme}{payload}' if scheme else None

    template = settings.get_happ_cryptolink_redirect_template()
    redirect_link = build_redirect_link(scheme_link, template) if scheme_link and template else None

    return redirect_link or scheme_link or subscription_url


def get_reset_devices_confirm_keyboard(language: str = 'ru') -> InlineKeyboardMarkup:
    get_texts(language)
    return InlineKeyboardMarkup(
        inline_keyboard=[
            [InlineKeyboardButton(text='✅ Да, сбросить все устройства', callback_data='confirm_reset_devices')],
            [InlineKeyboardButton(text='❌ Отмена', callback_data='menu_subscription')],
        ]
    )


def get_traffic_switch_keyboard(
    current_traffic_gb: int,
    language: str = 'ru',
    subscription_end_date: datetime = None,
    discount_percent: int = 0,
    base_traffic_gb: int = None,
) -> InlineKeyboardMarkup:
    from app.config import settings

    # Если базовый трафик не передан, используем текущий
    # (для обратной совместимости и случаев без докупленного трафика)
    if base_traffic_gb is None:
        base_traffic_gb = current_traffic_gb

    months_multiplier = 1
    period_text = ''
    if subscription_end_date:
        months_multiplier = get_remaining_months(subscription_end_date)
        if months_multiplier > 1:
            period_text = f' (за {months_multiplier} мес)'

    packages = settings.get_traffic_packages()
    enabled_packages = [pkg for pkg in packages if pkg['enabled']]

    # Используем базовый трафик для определения цены текущего пакета
    current_price_per_month = settings.get_traffic_price(base_traffic_gb)
    discounted_current_per_month, _ = apply_percentage_discount(
        current_price_per_month,
        discount_percent,
    )

    buttons = []

    for package in enabled_packages:
        gb = package['gb']
        price_per_month = package['price']
        discounted_price_per_month, _ = apply_percentage_discount(
            price_per_month,
            discount_percent,
        )

        price_diff_per_month = discounted_price_per_month - discounted_current_per_month
        total_price_diff = price_diff_per_month * months_multiplier

        # Сравниваем с базовым трафиком (без докупленного)
        if gb == base_traffic_gb:
            emoji = '✅'
            action_text = ' (текущий)'
            price_text = ''
        elif total_price_diff > 0:
            emoji = '⬆️'
            action_text = ''
            price_text = f' (+{total_price_diff // 100}₽{period_text})'
            if discount_percent > 0:
                discount_total = (price_per_month - current_price_per_month) * months_multiplier - total_price_diff
                if discount_total > 0:
                    price_text += f' (скидка {discount_percent}%: -{discount_total // 100}₽)'
        elif total_price_diff < 0:
            emoji = '⬇️'
            action_text = ''
            price_text = ' (без возврата)'
        else:
            emoji = '🔄'
            action_text = ''
            price_text = ' (бесплатно)'

        if gb == 0:
            traffic_text = 'Безлимит'
        else:
            traffic_text = f'{gb} ГБ'

        button_text = f'{emoji} {traffic_text}{action_text}{price_text}'

        buttons.append([InlineKeyboardButton(text=button_text, callback_data=f'switch_traffic_{gb}')])

    language_code = (language or 'ru').split('-')[0].lower()
    buttons.append(
        [
            InlineKeyboardButton(
                text='⬅️ Назад' if language_code in {'ru', 'fa'} else '⬅️ Back',
                callback_data='subscription_settings',
            )
        ]
    )

    return InlineKeyboardMarkup(inline_keyboard=buttons)


def get_confirm_switch_traffic_keyboard(
    new_traffic_gb: int, price_difference: int, language: str = 'ru'
) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup(
        inline_keyboard=[
            [
                InlineKeyboardButton(
                    text='✅ Подтвердить переключение',
                    callback_data=f'confirm_switch_traffic_{new_traffic_gb}_{price_difference}',
                )
            ],
            [InlineKeyboardButton(text='❌ Отмена', callback_data='subscription_settings')],
        ]
    )
