import platform
import time
import unicodedata
from collections.abc import Iterable, Sequence
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import Any


def _char_width(ch: str) -> int:
    """Return terminal display width of a single character."""
    cp = ord(ch)
    # Variation selector U+FE0F / U+FE0E — zero width (handled by caller)
    if cp in (0xFE0E, 0xFE0F, 0x200D):
        return 0
    # Combining marks — zero width
    if unicodedata.category(ch).startswith('M'):
        return 0
    # East Asian Wide / Fullwidth
    if unicodedata.east_asian_width(ch) in ('W', 'F'):
        return 2
    return 1


def _display_width(text: str) -> int:
    """Calculate terminal display width accounting for wide chars and emoji."""
    width = 0
    prev_base = 0
    for ch in text:
        cp = ord(ch)
        # U+FE0F emoji presentation selector — upgrades previous char to 2 cells
        if cp == 0xFE0F:
            if prev_base == 1:
                width += 1  # upgrade 1 → 2
                prev_base = 2
            continue
        cw = _char_width(ch)
        if cw > 0:
            prev_base = cw
        width += cw
    return width


def _ljust(text: str, width: int) -> str:
    """Left-justify text to given display width."""
    return text + ' ' * max(0, width - _display_width(text))


def _center(text: str, width: int) -> str:
    """Center text to given display width."""
    pad = max(0, width - _display_width(text))
    left = pad // 2
    return ' ' * left + text + ' ' * (pad - left)


@dataclass
class StepRecord:
    title: str
    icon: str
    status_label: str
    message: str
    duration: float


class StageHandle:
    def __init__(
        self,
        timeline: 'StartupTimeline',
        title: str,
        icon: str,
        success_message: str | None,
    ) -> None:
        self.timeline = timeline
        self.title = title
        self.icon = icon
        self.message = success_message or ''
        self.status_icon = '✅'
        self.status_label = 'Готово'
        self._explicit_status = False

    def success(self, message: str | None = None) -> None:
        if message is not None:
            self.message = message
        self.status_icon = '✅'
        self.status_label = 'Готово'
        self._explicit_status = True

    def warning(self, message: str) -> None:
        self.status_icon = '⚠️'
        self.status_label = 'Предупреждение'
        self.message = message
        self._explicit_status = True

    def skip(self, message: str) -> None:
        self.status_icon = '⏭️'
        self.status_label = 'Пропущено'
        self.message = message
        self._explicit_status = True

    def failure(self, message: str) -> None:
        self.status_icon = '❌'
        self.status_label = 'Ошибка'
        self.message = message
        self._explicit_status = True

    def log(self, message: str, icon: str = '•') -> None:
        self.timeline.logger.info('┃', icon=icon, message=message)


class StartupTimeline:
    def __init__(self, logger: Any, app_name: str) -> None:
        self.logger = logger
        self.app_name = app_name
        self.steps: list[StepRecord] = []

    def _record_step(self, title: str, icon: str, status_label: str, message: str, duration: float) -> None:
        self.steps.append(
            StepRecord(
                title=title,
                icon=icon,
                status_label=status_label,
                message=message,
                duration=duration,
            )
        )

    def log_banner(self, metadata: Sequence[tuple[str, Any]] | None = None) -> None:
        title_text = f'🚀 {self.app_name}'
        subtitle_parts = [f'Python {platform.python_version()}']
        if metadata:
            for key, value in metadata:
                subtitle_parts.append(f'{key}: {value}')
        subtitle_text = ' | '.join(subtitle_parts)

        width = max(_display_width(title_text), _display_width(subtitle_text))
        border = '╔' + '═' * (width + 2) + '╗'
        self.logger.info(border)
        self.logger.info('║ ' + _ljust(title_text, width) + ' ║')
        self.logger.info('║ ' + _ljust(subtitle_text, width) + ' ║')
        self.logger.info('╚' + '═' * (width + 2) + '╝')

    def log_section(self, title: str, lines: Iterable[str], icon: str = '📄') -> None:
        items = [f'{icon} {title}'] + [f'• {line}' for line in lines]
        width = max(_display_width(item) for item in items)
        top = '┌ ' + '─' * width + ' ┐'
        middle = '├ ' + '─' * width + ' ┤'
        bottom = '└ ' + '─' * width + ' ┘'

        self.logger.info(top)
        self.logger.info('│ ' + _ljust(items[0], width) + ' │')
        self.logger.info(middle)
        for item in items[1:]:
            self.logger.info('│ ' + _ljust(item, width) + ' │')
        self.logger.info(bottom)

    def add_manual_step(
        self,
        title: str,
        icon: str,
        status_label: str,
        message: str,
    ) -> None:
        self.logger.info('┏', icon=icon, title=title)
        self.logger.info('┗ —', icon=icon, title=title, status_label=status_label, message=message)
        self._record_step(title, icon, status_label, message, 0.0)

    @asynccontextmanager
    async def stage(
        self,
        title: str,
        icon: str = '⚙️',
        description: str | None = None,
        success_message: str | None = 'Готово',
    ):
        if description:
            self.logger.info('┏ —', icon=icon, title=title, description=description)
        else:
            self.logger.info('┏', icon=icon, title=title)

        handle = StageHandle(self, title, icon, success_message)
        start_time = time.perf_counter()
        try:
            yield handle
        except Exception as exc:
            message = str(exc)
            handle.failure(message)
            self.logger.exception('┣ ❌ — ошибка', title=title, message=message)
            raise
        finally:
            duration = time.perf_counter() - start_time
            if not handle._explicit_status:
                handle.success(handle.message or 'Готово')
            self.logger.info(
                '┗ — [s]',
                status_icon=handle.status_icon,
                title=title,
                message=handle.message,
                duration=round(duration, 2),
            )
            self._record_step(
                title=title,
                icon=handle.status_icon,
                status_label=handle.status_label,
                message=handle.message,
                duration=duration,
            )

    def log_summary(self) -> None:
        if not self.steps:
            return

        lines = []
        for step in self.steps:
            base = f'{step.icon} {step.title} — {step.status_label} [{step.duration:.2f}s]'
            if step.message:
                base += f' :: {step.message}'
            lines.append(base)

        width = max(_display_width(line) for line in lines)
        border_top = '┏' + '━' * (width + 2) + '┓'
        border_mid = '┣' + '━' * (width + 2) + '┫'
        border_bottom = '┗' + '━' * (width + 2) + '┛'
        title = 'РЕЗЮМЕ ЗАПУСКА'

        self.logger.info(border_top)
        self.logger.info('┃ ' + _center(title, width) + ' ┃')
        self.logger.info(border_mid)
        for line in lines:
            self.logger.info('┃ ' + _ljust(line, width) + ' ┃')
        self.logger.info(border_bottom)
