"""
tglib/sessions/string.py  —  String-encoded session.

TGLib — Copyright (C) Ankit Chaubey <ankitchaubey.dev@gmail.com>
GitHub  : https://github.com/ankit-chaubey/TGLib

Portions ported from Telethon v1 (https://github.com/LonamiWebs/Telethon)
Copyright (C) LonamiWebs — MIT License.
"""
import base64
import ipaddress
import struct

from .memory import MemorySession
from ..crypto import AuthKey

_STRUCT_PREFORMAT = '>B{}sH256s'
CURRENT_VERSION = '1'


class StringSession(MemorySession):
    """
    A session that serialises to/from a base64 URL-safe string.

    Ideal for serverless environments, environment variables, databases,
    or anywhere you don't want on-disk files.

    Usage::

        from tglib import TelegramClient
        from tglib.sessions import StringSession

        # First run: generate and print the string
        client = TelegramClient(StringSession(), api_id, api_hash)
        await client.start(phone)
        print(client.session.save())   # keep this string!

        # Later: load from the saved string
        client = TelegramClient(StringSession("1AQA..."), api_id, api_hash)

    The string starts with the version character ``'1'``.
    """

    def __init__(self, string: str = None):
        super().__init__()
        if string:
            if string[0] != CURRENT_VERSION:
                raise ValueError(
                    f'StringSession version {string[0]!r} is not supported '
                    f'(expected {CURRENT_VERSION!r}). '
                    'This may have been generated by a newer version of tglib.'
                )
            string = string[1:]
            ip_len = 4 if len(string) == 352 else 16
            try:
                self._dc_id, ip, self._port, key = struct.unpack(
                    _STRUCT_PREFORMAT.format(ip_len),
                    StringSession.decode(string)
                )
            except struct.error as e:
                raise ValueError(f'Malformed StringSession: {e}') from e

            self._server_address = ipaddress.ip_address(ip).compressed
            if any(key):
                self._auth_key = AuthKey(key)

    # ── Codec ──────────────────────────────────────────────────────────────────

    @staticmethod
    def encode(data: bytes) -> str:
        """Base64-urlsafe encode bytes to ASCII string."""
        return base64.urlsafe_b64encode(data).decode('ascii')

    @staticmethod
    def decode(text: str) -> bytes:
        """Base64-urlsafe decode string to bytes."""
        return base64.urlsafe_b64decode(text)

    # ── Session interface ──────────────────────────────────────────────────────

    def save(self) -> str:
        """
        Serialise the current session to a string.
        Returns an empty string if there is no auth key yet.
        """
        if not self._auth_key:
            return ''

        ip = ipaddress.ip_address(self._server_address).packed
        payload = struct.pack(
            _STRUCT_PREFORMAT.format(len(ip)),
            self._dc_id,
            ip,
            self._port,
            self._auth_key.key,
        )
        return CURRENT_VERSION + StringSession.encode(payload)

    def close(self):
        """No-op (nothing to close for in-memory session)."""
        pass

    def __str__(self) -> str:
        return self.save()

    def __repr__(self) -> str:
        return f'StringSession({self.save()!r})'
