import os
import shutil
from pathlib import Path
from typing import Optional

from mmar_envops.config import ExecConfig
from mmar_envops.stdout import print_debug, print_time_debug
from mmar_envops.system import create_link, shell_raw, shell_to_lines
from mmar_envops.utils import noop
from mmar_envops.stdout import COL, COL_DUMMY
from mmar_envops.repl import Asker


class DryModeInterrupt(InterruptedError):
    """
    Custom exception used during dry mode operations.

    Attributes:
        msg (Optional[str]): An optional custom message associated with the interrupt.
    """

    YELLOW_COLOR_CODE = "\033[93m"
    RESET_COLOR_CODE = "\033[0m"

    def __init__(self, msg: Optional[str] = None):
        super().__init__()
        self.msg = msg

    def __str__(self):
        base_message = "Operation interrupted due to dry mode."
        if self.msg:
            return f"{base_message}\nAdditional info: {self.msg}"
        return base_message

    @classmethod
    def raise_with_yellow_message(cls, ex: InterruptedError):
        """Raises the exception with a yellow-colored message."""
        print(f"{cls.YELLOW_COLOR_CODE}{ex}{cls.RESET_COLOR_CODE}")
        raise cls from ex


class ExecContext:
    def __init__(self, ecfg: ExecConfig):
        self.ecfg = ecfg
        self.dry = self.ecfg.dry
        self.debug = (print_time_debug if self.ecfg.time else print_debug) if self.ecfg.debug else noop
        self.col = COL if self.ecfg.color else COL_DUMMY
        self.asker = Asker(self.ecfg.autoyes)

    def system(self, cmd) -> str | None:
        cmd = cmd.strip()
        self.debug(f"Evaluating cmd: {cmd} ( in dir: {os.getcwd()} )")
        if self.ecfg.dry:
            return None
        exit_code = int(shell_raw(cmd))
        if exit_code == 0:
            return None
        msg = f"Bad exit code: {exit_code}"
        result = self.col.red(msg)
        return result if isinstance(result, str) else None

    def system_get_lines(self, cmd, cwd=None, timeout: int | None = None) -> tuple[Exception | None, list[str] | None]:
        cwd = cwd or os.getcwd()
        msg_cmd = f"{cmd} ( in dir: {cwd} )"
        self.debug(f"Evaluating cmd to lines: {msg_cmd}")
        if self.ecfg.dry:
            return DryModeInterrupt(f"dry-mode: {msg_cmd}"), None
        from mmar_envops.system import ShellConfig

        shell_cfg = ShellConfig(color=self.ecfg.color, ripgrep_max_columns=self.ecfg.ripgrep_max_columns)
        ex, res = shell_to_lines(cmd, cwd, debug=self.debug, cfg_shell=shell_cfg, timeout=timeout)
        return ex, res

    def rmdir_if_exist(self, path: Path):
        if not path.exists():
            return
        self.debug(f"Going to remove dir: {path}")
        if self.ecfg.dry:
            return
        shutil.rmtree(path)
        print(f"Removed directory: {path}")

    def mkdir_if_not_exist(self, path: Path):
        if path.exists():
            return
        self.debug(f"Going to create dir: {path}")
        if self.ecfg.dry:
            return
        try:
            path.mkdir(exist_ok=True, parents=True)
        except PermissionError:
            cmd = f"sudo mkdir {str(path)}"
            print(f"Need sudo to create dir: {path} ( command: {cmd} )")
            shell_raw(cmd)
        print(f"Created directory: {path}")

    def unlink_if_needed(self, l_dir):
        self.debug(f"Going to unlink: {l_dir}")
        if self.ecfg.dry:
            return
        if not l_dir.is_symlink():
            return
        l_dir.unlink()

    def create_link(self, link_path, target_path):
        if not link_path or not target_path:
            return
        if link_path.exists():
            print(f"Link already exists: {link_path} -> {target_path}")
            return
        if self.ecfg.dry:
            print(f"Going to create {link_path} -> {target_path}, but dry-mode")
            return
        create_link(target_path, link_path)
        print(f"Created link: {link_path} -> {target_path}")

    def clone_if_needed(self, repo_url, repo_path):
        if repo_path.exists():
            self.debug(f"Path {repo_path} already exists, skipped cloning from {repo_url}")
            return
        qu = f"Not exist path for repo `{repo_url}` at `{repo_path}`, clone?"
        if not self.asker.ask_is_yes(qu):
            return
        cmd = f"git clone {repo_url} {repo_path}"
        err_msg = self.system(cmd)
        if err_msg:
            print(err_msg)
