"""Store extra error details."""

import sys
from contextlib import AbstractContextManager
from datetime import datetime
from io import StringIO
from pathlib import Path

from msgspec import Struct
from msgspec._core import json_encode, json_format
from pytz import UTC
from rich.console import Console
from rich.traceback import Trace, Traceback

from slices_cli_bi import console, err_console
from slices_cli_bi.core.const import CONFIG_DIR
from slices_cli_bi.util.shell import shlex_quote_if_needed


class ErrorDetailsHandler(AbstractContextManager):
    """A context manager for storing extra error details.

    ErrorDetailsHandler is typically created when an error occurs.
    The contextmanager exits and writes an error details file when the Exit is raised.
    It also outputs a message to the user, showing where the file is written.
    """

    def __init__(self):
        self.console_io = StringIO()
        self.console = Console(
            file=self.console_io,
            force_terminal=False,
            force_jupyter=False,
            force_interactive=False,
            width=140,
            height=30,
            no_color=True,
        )
        self._objects: list[tuple[str, Struct]] = []
        self._traces: list[Trace] = []
        # You can make an ErrorDetailsHandler before you now there's an error,
        # and only set this to True when the error occurs
        # This allows older context to be logged.
        self.must_store_failure = True

    def __exit__(self, *exc_details):
        """When exiting the ErrorDetailsHandler context manager, write details to file."""
        if self.must_store_failure:
            self._store_failure()

    def print(self, *args, **kwargs):
        """Print to stderr console."""
        err_console.print(*args, **kwargs)
        self.console.print(*args, **kwargs)

    def print_stdout(self, *args, **kwargs):
        """Print to stdout console."""
        console.print(*args, **kwargs)
        self.console.print(*args, **kwargs)

    def add_object(self, name: str, obj: Struct):
        """Add an object to the Error Details."""
        self._objects.append((name, obj))

    def add_cur_trace(self):
        """Add the current trace to the Error Details."""
        exc_type, exc_value, traceback = sys.exc_info()
        if exc_type is None or exc_value is None or traceback is None:
            self.console.print("ErrorDetailsHandler.add_cur_trace should be called within except block!")
            return
        self._traces.append(Traceback.extract(exc_type, exc_value, traceback, show_locals=True))

    def _store_failure(self) -> Path:
        error_details_dir = Path.home() / CONFIG_DIR / "cli_error_details"
        if not error_details_dir.exists():
            error_details_dir.mkdir()
        now = datetime.now(UTC)
        err_file = error_details_dir / f"err_{now.isoformat(sep='T', timespec='seconds')}.txt"
        with err_file.open("w") as f:
            file_console = Console(
                file=f,
                force_terminal=False,
                force_jupyter=False,
                force_interactive=False,
                width=140,
                height=30,
                no_color=True,
            )

            def print_head(txt: str):
                count = 4 + len(txt)
                file_console.print("#" * count)
                file_console.print(f"# {txt} #")
                file_console.print("#" * count)

            print_head("Error details.")
            command = " ".join([shlex_quote_if_needed(a) for a in sys.argv])
            file_console.print(f"Command: {command}")
            file_console.print(f"Date: {datetime.now(UTC)}")
            file_console.print()

            print_head("Console Error Output")
            file_console.print(self.console_io.getvalue())
            file_console.print()

            for name, obj in self._objects:
                print_head(f"Object {name!r}")
                file_console.print(json_format(json_encode(obj).decode()))
                file_console.print()

            for index, trace in enumerate(self._traces):
                print_head(f"Traceback {index}")
                file_console.print(Traceback(trace))
                file_console.print()

        console.print(f"[bright_black]Details stored at: {str(err_file.absolute())}", highlight=False)
        return err_file
