import contextlib
import linecache
import sys
import time
import threading
from pathlib import Path
from typing import Any, Optional, TextIO, Union, Callable

from .exceptions import InvalidOutputError


class Timer:
    def __init__(self):
        self.start_time = time.perf_counter()

    def elapsed(self) -> float:
        return time.perf_counter() - self.start_time

    def reset(self):
        self.start_time = time.perf_counter()


def get_thread_info() -> str:
    thread = threading.current_thread()
    return f"[{thread.name}-{thread.ident}] "


def ensure_path(path: Union[str, Path]) -> Path:
    if isinstance(path, str):
        return Path(path)
    return path


def open_output_stream(
    output: Optional[Union[str, Path, TextIO, Callable[[str], None]]],
    overwrite: bool = False
) -> Union[TextIO, Callable[[str], None]]:
    if output is None:
        return sys.stderr

    if isinstance(output, (str, Path)):
        path = ensure_path(output)
        mode = 'w' if overwrite else 'a'
        try:
            return open(path, mode, buffering=1)
        except Exception as e:
            raise InvalidOutputError(f"can not open file {path}: {e}")

    if hasattr(output, 'write') and callable(output.write):
        return output

    if callable(output):
        return output

    raise InvalidOutputError(f"error output type: {type(output)}")


def close_output_stream(stream: Union[TextIO, Callable[[str], None]], opened_by_us: bool):
    if opened_by_us and hasattr(stream, 'close') and callable(stream.close):
        with contextlib.suppress(Exception):
            stream.close()


def is_simple_value(value: Any) -> bool:
    simple_types = (int, float, bool, type(None))
    if isinstance(value, simple_types):
        return True

    if isinstance(value, str):
        return len(value) <= 50

    if isinstance(value, (list, tuple, set, dict)):
        return len(str(value)) <= 50

    return False


def format_value(value: Any, max_length: int = 10000) -> str:
    try:
        if isinstance(value, (int, float, bool, type(None))):
            return repr(value)

        if isinstance(value, str):
            if len(value) > max_length:
                return repr(value[:max_length-3] + "...")
            return repr(value)

        if isinstance(value, (list, tuple, set)):
            if len(value) <= 20:
                repr_str = repr(value)
                if len(repr_str) <= max_length:
                    return repr_str

            return f"<{type(value).__name__} with {len(value)} items: {list(value)[:5]}...>"

        if isinstance(value, dict):
            if len(value) <= 50:
                repr_str = repr(value)
                if len(repr_str) <= max_length:
                    return repr_str
                else:
                    if len(repr_str) > max_length:
                        return repr_str[:max_length-3] + "...}"

            keys = list(value.keys())[:5]
            preview = {k: value[k] for k in keys}
            return f"<dict with {len(value)} items: {preview}...>"

        if hasattr(value, '__name__'):
            if hasattr(value, 'cr_await') or hasattr(value, '__await__'):
                return f"<coroutine {value.__name__}>"
            if callable(value):
                return f"<function {value.__name__}>"

        class_name = type(value).__name__
        return f"<{class_name} object>"

    except Exception as e:
        return f"<display error: {e}>"


def get_source_line(filename: str, lineno: int) -> str:
    with contextlib.suppress(Exception):
        line = linecache.getline(filename, lineno)
        if line:
            return line.strip()

    return "SOURCE IS UNAVAILABLE"


class OutputWriter:
    def __init__(self, stream: Union[TextIO, Callable[[str], None]]):
        self.stream = stream

    def write(self, text: str):
        if callable(self.stream):
            self.stream(text)
        else:
            self.stream.write(text)
            self.stream.flush()

    def close(self):
        if hasattr(self.stream, 'close') and callable(self.stream.close):
            self.stream.close()
