import argparse
import os
import sys
from pathlib import Path
import atexit

from . import __version__
from .interpreter import Interpreter
from .lexer import Lexer
from .parser import FunctionCall, ParseError, Parser
from .tokens import TokenType
from .runner import run_file
from .formatter import format_source
from .test_runner import format_failure, run_tests
from .docs_generator import generate_markdown


VERSION = f"Prasad Language v{__version__}"


def _write_stdout(text: str) -> None:
    try:
        sys.stdout.write(text)
    except UnicodeEncodeError:
        encoding = sys.stdout.encoding or "utf-8"
        data = text.encode(encoding, errors="backslashreplace")
        if hasattr(sys.stdout, "buffer"):
            sys.stdout.buffer.write(data)
        else:
            # Fallback: last-resort lossy output
            sys.stdout.write(data.decode(encoding, errors="replace"))


def repl():
    print(VERSION)
    print("Type 'exit' to quit")
    print()

    interpreter = Interpreter(base_dir=os.getcwd())

    readline = None
    history_path = None
    try:
        import readline as _readline  # type: ignore

        readline = _readline
        history_path = os.path.join(os.path.expanduser("~"), ".prasad_history")
        try:
            readline.read_history_file(history_path)
        except Exception:
            pass

        def _save_history():
            try:
                if history_path:
                    readline.write_history_file(history_path)
            except Exception:
                pass

        atexit.register(_save_history)
    except Exception:
        pass

    def _needs_more_input(source: str) -> bool:
        try:
            tokens = Lexer(source).tokenize()
        except Exception:
            # Let lexer errors surface immediately (unexpected char, unterminated string, ...)
            return False

        brace = paren = bracket = 0
        last_sig = None
        for t in tokens:
            if t.type == TokenType.EOF:
                break
            if t.type == TokenType.LBRACE:
                brace += 1
            elif t.type == TokenType.RBRACE:
                brace -= 1
            elif t.type == TokenType.LPAREN:
                paren += 1
            elif t.type == TokenType.RPAREN:
                paren -= 1
            elif t.type == TokenType.LBRACKET:
                bracket += 1
            elif t.type == TokenType.RBRACKET:
                bracket -= 1

            if t.type not in (TokenType.SEMICOLON,):
                last_sig = t

        if brace > 0 or paren > 0 or bracket > 0:
            return True

        if last_sig is not None and last_sig.type == TokenType.OPERATOR:
            # trailing operator like "a +"
            return True

        return False

    def _print_repl_error(err: Exception):
        msg = str(err)
        if msg.startswith("Error at line ") or msg.startswith("Chuk:"):
            print(msg)
        else:
            print(f"Error: {msg}")

    while True:
        try:
            line = input(">> ")
        except (EOFError, KeyboardInterrupt):
            print()
            return

        buffer = line
        stripped = buffer.strip()

        if not stripped:
            continue
        if stripped in ("exit", "quit"):
            return

        while _needs_more_input(buffer):
            try:
                more = input(".. ")
            except (EOFError, KeyboardInterrupt):
                print()
                return
            buffer = buffer + "\n" + more

        try:
            tokens = Lexer(buffer).tokenize()

            # Try statement/program form first (assignments, dakhav(...), etc.)
            try:
                program = Parser(tokens, source=buffer).parse()
                if (
                    len(program.statements) == 1
                    and isinstance(program.statements[0], FunctionCall)
                ):
                    value = interpreter.eval_expr(program.statements[0])
                    if value is not None:
                        print(value)
                else:
                    interpreter.run(program)

                if readline is not None:
                    try:
                        readline.add_history(buffer)
                    except Exception:
                        pass
                continue
            except ParseError:
                pass

            # Fallback: treat the line as an expression and print its value.
            if "\n" in buffer:
                raise ParseError("Chuk: Incomplete/invalid multi-line input")

            parser = Parser(tokens, source=buffer)
            expr = parser.expression()
            parser.maybe_semicolon()
            if parser.current().type != TokenType.EOF:
                raise ParseError("Chuk: Unexpected extra input")

            value = interpreter.eval_expr(expr)
            if value is not None:
                print(value)

            if readline is not None:
                try:
                    readline.add_history(buffer)
                except Exception:
                    pass
        except Exception as e:
            _print_repl_error(e)


def main():
    if len(sys.argv) > 1 and sys.argv[1] == "fmt":
        fmt_parser = argparse.ArgumentParser(
            prog="prasad fmt",
            description="Format a .prasad file",
        )
        fmt_parser.add_argument("file", help="Path to .prasad file")
        fmt_parser.add_argument(
            "-w", "--write", action="store_true", help="Write formatted output back to the file"
        )
        args = fmt_parser.parse_args(sys.argv[2:])

        with open(args.file, "r", encoding="utf-8") as f:
            source = f.read()
        formatted = format_source(source)

        if args.write:
            with open(args.file, "w", encoding="utf-8") as f:
                f.write(formatted)
        else:
            _write_stdout(formatted)
        return

    if len(sys.argv) > 1 and sys.argv[1] == "test":
        test_parser = argparse.ArgumentParser(
            prog="prasad test",
            description="Run Prasad golden-file tests from the tests/ folder",
        )
        test_parser.add_argument(
            "--dir", default="tests", help="Tests directory (default: tests)"
        )
        args = test_parser.parse_args(sys.argv[2:])

        code, results = run_tests(args.dir)
        if not results:
            print(f"No tests found in '{args.dir}'")
            raise SystemExit(1)

        passed = sum(1 for r in results if r.passed)
        failed = len(results) - passed

        for r in results:
            status = "PASS" if r.passed else "FAIL"
            print(f"{status} {r.path}")
            if not r.passed:
                print(format_failure(r))
                print()

        print(f"{passed} passed, {failed} failed")
        raise SystemExit(code)

    if len(sys.argv) > 1 and sys.argv[1] == "docs":
        docs_parser = argparse.ArgumentParser(
            prog="prasad docs",
            description="Generate Markdown docs by extracting comments from .prasad files",
        )
        docs_parser.add_argument(
            "--dir",
            action="append",
            default=[],
            help="Directory or file to scan (repeatable). Defaults to examples/ and tests/ if omitted.",
        )
        docs_parser.add_argument(
            "--title", default="Prasad Language Docs", help="Top-level Markdown title"
        )
        docs_parser.add_argument(
            "-o",
            "--out",
            default="docs/generated.md",
            help="Output markdown file (default: docs/generated.md). Use '-' for stdout.",
        )
        args = docs_parser.parse_args(sys.argv[2:])

        roots = args.dir if args.dir else ["examples", "tests"]
        md = generate_markdown(*roots, title=args.title)

        if args.out == "-":
            _write_stdout(md)
            return

        out_path = Path(args.out)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(md, encoding="utf-8")
        print(f"Wrote {out_path}")
        return

    if len(sys.argv) > 1 and sys.argv[1] == "run":
        run_parser = argparse.ArgumentParser(
            prog="prasad run",
            description="Run a Prasad package (directory containing prasad.toml)",
        )
        run_parser.add_argument(
            "path",
            nargs="?",
            default=".",
            help="Package directory or prasad.toml path (default: .)",
        )
        args = run_parser.parse_args(sys.argv[2:])
        run_file(args.path)
        return

    parser = argparse.ArgumentParser(
        prog="prasad",
        description="Prasad Programming Language CLI",
    )

    parser.add_argument("file", nargs="?", help="Path to .prasad file")

    parser.add_argument("--version", action="store_true", help="Show Prasad version")

    args = parser.parse_args()

    if args.version:
        print(VERSION)
        return

    if args.file:
        run_file(args.file)
    else:
        repl()
