import re
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime

from rich.console import Console, ConsoleOptions, RenderResult
from rich.table import Table
from rich.text import Text

from epstein_files.documents.communication import Communication
from epstein_files.documents.imessage.text_message import MSG_DATE_FORMAT, TextMessage
from epstein_files.util.constant.names import JEFFREY_EPSTEIN, UNKNOWN
from epstein_files.util.constant.strings import AUTHOR
from epstein_files.util.data import iso_timestamp, listify, sort_dict
from epstein_files.util.doc_cfg import Metadata, TextCfg
from epstein_files.util.highlighted_group import get_style_for_name
from epstein_files.util.logging import logger

CONFIRMED_MSG = 'Found confirmed counterparty'
GUESSED_MSG = 'This is probably a conversation with'
MSG_REGEX = re.compile(r'Sender:(.*?)\nTime:(.*? (AM|PM)).*?Message:(.*?)\s*?((?=(\nSender)|\Z))', re.DOTALL)
REDACTED_AUTHOR_REGEX = re.compile(r"^([-+•_1MENO.=F]+|[4Ide])$")


@dataclass
class MessengerLog(Communication):
    """Class representing one iMessage log file (one conversation between Epstein and some counterparty)."""
    config: TextCfg | None = None
    _messages: list[TextMessage] = field(default_factory=list)

    def first_message_at(self, name: str | None) -> datetime:
        return self.messages_by(name)[0].timestamp()

    def info_txt(self) -> Text | None:
        if self.author is None:
            return None

        hint_msg = GUESSED_MSG if self.is_attribution_uncertain() else CONFIRMED_MSG
        author_txt = Text(self.author_or_unknown(), style=self.author_style + ' bold')
        return Text(f"({hint_msg} ", style='dim').append(author_txt).append(')')

    def last_message_at(self, name: str | None) -> datetime:
        return self.messages_by(name)[-1].timestamp()

    def messages(self) -> list[TextMessage]:
        """Lazily evaluated accessor for self._messages."""
        if len(self._messages) == 0:
            self._messages = [
                TextMessage(
                    # If the Sender: is redacted that means it's from self.author
                    author=REDACTED_AUTHOR_REGEX.sub('', match.group(1).strip()) or self.author,
                    id_confirmed=not self.is_attribution_uncertain(),
                    text=match.group(4).strip(),
                    timestamp_str=match.group(2).strip(),
                )
                for match in MSG_REGEX.finditer(self.text)
            ]

        return self._messages

    def messages_by(self, name: str | None) -> list[TextMessage]:
        """Return all messages by 'name'."""
        return [m for m in self.messages() if m.author == name]

    def metadata(self) -> Metadata:
        metadata = super().metadata()
        metadata.update({'num_messages': len(self.messages())})
        return metadata

    def _border_style(self) -> str:
        return self.author_style

    def _extract_timestamp(self) -> datetime:
        for match in MSG_REGEX.finditer(self.text):
            timestamp_str = match.group(2).strip()

            try:
                return datetime.strptime(timestamp_str, MSG_DATE_FORMAT)
            except ValueError as e:
                logger.info(f"Failed to parse '{timestamp_str}' to datetime! Using next match. Error: {e}'")

        raise RuntimeError(f"{self}: No timestamp found!")

    def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
        yield self.file_info_panel()
        yield Text('')

        for message in self.messages():
            yield message

    @classmethod
    def count_authors(cls, imessage_logs: list['MessengerLog']) -> dict[str | None, int]:
        """Count up how many texts were sent by each author."""
        sender_counts: dict[str | None, int] = defaultdict(int)

        for message_log in imessage_logs:
            for message in message_log.messages():
                sender_counts[message.author] += 1

        return sender_counts

    @classmethod
    def logs_for(cls, author: str | None | list[str | None], logs: list['MessengerLog']) -> list['MessengerLog']:
        authors = listify(author)
        return logs if JEFFREY_EPSTEIN in authors else [log for log in logs if log.author in authors]

    @classmethod
    def summary_table(cls, imessage_logs: list['MessengerLog']) -> Table:
        """Build a table summarizing the text messages in 'imessage_logs'."""
        counts_table = Table(title="Text Message Counts By Author", header_style="bold")
        counts_table.add_column(AUTHOR.title(), justify='left', style="steel_blue bold", width=30)
        counts_table.add_column('Files', justify='right', style='white')
        counts_table.add_column("Msgs", justify='right')
        counts_table.add_column('First Sent At', justify='center', highlight=True, width=21)
        counts_table.add_column('Last Sent At', justify='center', style='wheat4', width=21)
        counts_table.add_column('Days', justify='right', style='dim')

        for name, count in sort_dict(cls.count_authors(imessage_logs)):
            logs = cls.logs_for(name, imessage_logs)
            first_at = logs[0].first_message_at(name)
            last_at = logs[-1].first_message_at(name)

            counts_table.add_row(
                Text(name or UNKNOWN, get_style_for_name(name)),
                str(len(logs)),
                f"{count:,}",
                iso_timestamp(first_at),
                iso_timestamp(last_at),
                str((last_at - first_at).days + 1),
            )

        return counts_table
