#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : sepinetam@gmail.com
# @File   : do.py

import logging
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Dict, List, Optional

from ....utils import get_nowtime


class StataDo:
    def __init__(self,
                 stata_cli: str,
                 log_file_path: Path,
                 is_unix: bool = None,
                 cwd: Path = None,
                 monitors: Optional[List] = None):
        """
        Initialize Stata executor

        Args:
            stata_cli: Path to Stata command line tool
            log_file_path: Path for storing log files
            is_unix: Whether the OS is Unix-like (macOS/Linux)
            cwd (Path): current working directory
            monitors: List of monitor instances (e.g., RAMMonitor, TimeoutMonitor)
        """
        self.stata_cli = stata_cli
        self.log_file_path = log_file_path
        if is_unix is not None:
            self.is_unix = is_unix
        else:
            from ....utils import get_os
            self.is_unix = get_os() in ["Darwin", "Linux"]
        self.cwd = cwd or Path.cwd()
        self.monitors = monitors or []
        self.IS_MONITOR = len(self.monitors) > 0

    def set_cli(self, cli_path):
        self.stata_cli = cli_path

    @property
    def STATA_CLI(self):
        return self.stata_cli

    def execute_dofile(self,
                       dofile_path: Path,
                       log_file_name: str = None,
                       is_replace: bool = True) -> Path:
        """
        Execute Stata do file and return log file path

        Args:
            dofile_path (Path): Path to do file
            log_file_name (str, optional): File name of log
            is_replace (bool): Whether replace the log file if exists before. Default is True

        Returns:
            Path: Path to generated log file

        Raises:
            ValueError: Unsupported operating system
            RuntimeError: Stata execution error
        """
        nowtime = get_nowtime()
        log_name = log_file_name or nowtime
        log_file = self.log_file_path / f"{log_name}.log"

        if self.is_unix:
            if self.IS_MONITOR:
                self._execute_unix_like_with_monitors(dofile_path, log_file, is_replace)
            else:
                self._execute_unix_like(dofile_path, log_file, is_replace)
        else:
            if self.IS_MONITOR:
                self._execute_windows_with_monitors(dofile_path, log_file, is_replace)
            else:
                self._execute_windows(dofile_path, log_file, is_replace)

        return log_file

    @staticmethod
    def set_fake_terminal_size_env(columns: str | int = '120',
                                   lines: str | int = '40') -> Dict[str, str]:
        env = os.environ.copy()
        env['COLUMNS'] = str(columns)
        env['LINES'] = str(lines)
        return env

    def _execute_unix_like(self, dofile_path: Path, log_file: Path, is_replace: bool = True):
        """
        Execute Stata on macOS/Linux systems

        Args:
            dofile_path: Path to do file
            log_file: Path to log file
            is_replace: Whether replace the log file if exists.

        Raises:
            RuntimeError: Stata execution error
        """
        # Get environment with terminal size settings
        env = self.set_fake_terminal_size_env()

        proc = subprocess.Popen(
            [self.STATA_CLI],  # Launch the Stata CLI
            stdin=subprocess.PIPE,  # Prepare to send commands
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            shell=False,  # Direct execution, subprocess handles path spaces safely
            env=env,  # Use environment with terminal size settings
            cwd=self.cwd  # Set cwd for more friendly control output
        )

        # Execute commands sequentially in Stata
        replace_clause = ", replace" if is_replace else ""

        commands = f"""
        capture log close
        log using "{log_file}"{replace_clause}
        do "{dofile_path}"
        log close
        exit, STATA
        """
        _, stderr = proc.communicate(input=commands)  # Send commands and wait for completion

        if proc.returncode != 0:
            logging.error(f"Stata execution failed: {stderr}")
            raise RuntimeError(f"Something went wrong: {stderr}")
        else:
            logging.info(f"Stata execution completed successfully. Log file: {log_file}")

    def _execute_windows(self, dofile_path: Path, log_file: Path, is_replace: bool = True):
        """
        Execute Stata on Windows systems

        Args:
            dofile_path: Path to do file
            log_file: Path to log file
            is_replace: Whether replace the log file if exists.
        """
        # Windows approach - use the /e flag to run a batch command
        # Create a temporary batch file in system temp directory
        batch_file = Path(tempfile.gettempdir()) / f"stata_batch__{dofile_path.stem}.do"

        replace_clause = ", replace" if is_replace else ""
        try:
            with open(batch_file, "w", encoding="utf-8") as f:
                f.write("capture log close\n")
                f.write(f'log using "{log_file}"{replace_clause}\n')
                f.write(f'do "{dofile_path}"\n')
                f.write("log close\n")
                f.write("exit, STATA\n")

            # Run Stata on Windows using /e to execute the batch file
            # Use double quotes to handle spaces in the path
            cmd = f'"{self.STATA_CLI}" /e do "{batch_file}"'
            result = subprocess.run(
                cmd,
                shell=True,
                capture_output=True,
                text=True,
                cwd=self.cwd
            )

            if result.returncode != 0:
                logging.error(f"Stata execution failed on Windows: {result.stderr}")
                raise RuntimeError(f"Windows Stata execution failed: {result.stderr}")
            else:
                logging.info(f"Stata execution completed successfully on Windows. Log file: {log_file}")

        except Exception as e:
            logging.error(f"Error during Windows Stata execution: {str(e)}")
            raise
        finally:
            # Clean up temporary batch file
            if batch_file.exists():
                try:
                    batch_file.unlink()
                    logging.debug(f"Temporary batch file removed: {batch_file}")
                except Exception as e:
                    logging.warning(f"Failed to remove temporary batch file {batch_file}: {str(e)}")

    def _execute_unix_like_with_monitors(self, dofile_path: Path, log_file: Path, is_replace: bool = True):
        """
        Execute Stata on macOS/Linux systems with monitoring enabled.

        Args:
            dofile_path: Path to do file
            log_file: Path to log file
            is_replace: Whether replace the log file if exists.

        Raises:
            RuntimeError: Stata execution error
        """
        # Get environment with terminal size settings
        env = self.set_fake_terminal_size_env()

        proc = subprocess.Popen(
            [self.STATA_CLI],  # Launch the Stata CLI
            stdin=subprocess.PIPE,  # Prepare to send commands
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            shell=False,  # Direct execution, subprocess handles path spaces safely
            env=env,  # Use environment with terminal size settings
            cwd=self.cwd  # Set cwd for more friendly control output
        )

        # Execute commands sequentially in Stata
        replace_clause = ", replace" if is_replace else ""

        commands = f"""
        capture log close
        log using "{log_file}"{replace_clause}
        do "{dofile_path}"
        log close
        exit, STATA
        """

        # Start all monitors
        if self.monitors:
            logging.info(f"Starting {len(self.monitors)} monitor(s)")
        for monitor in self.monitors:
            monitor.start(proc)

        _, stderr = proc.communicate(input=commands)  # Send commands and wait for completion

        # Stop all monitors (this will raise exceptions if limits were exceeded)
        if self.monitors:
            logging.info("Stopping all monitors")
        for monitor in self.monitors:
            monitor.stop()

        if proc.returncode != 0:
            logging.error(f"Stata execution failed: {stderr}")
            raise RuntimeError(f"Something went wrong: {stderr}")
        else:
            logging.info(f"Stata execution completed successfully. Log file: {log_file}")

    def _execute_windows_with_monitors(self, dofile_path: Path, log_file: Path, is_replace: bool = True):
        """
        Execute Stata on Windows systems with monitoring enabled.

        Args:
            dofile_path: Path to do file
            log_file: Path to log file
            is_replace: Whether replace the log file if exists.

        Raises:
            RuntimeError: Stata execution error
        """
        # Windows approach - use the /e flag to run a batch command
        # Create a temporary batch file in system temp directory
        batch_file = Path(tempfile.gettempdir()) / f"stata_batch__{dofile_path.stem}.do"

        replace_clause = ", replace" if is_replace else ""
        try:
            with open(batch_file, "w", encoding="utf-8") as f:
                f.write("capture log close\n")
                f.write(f'log using "{log_file}"{replace_clause}\n')
                f.write(f'do "{dofile_path}"\n')
                f.write("log close\n")
                f.write("exit, STATA\n")

            # Run Stata on Windows using /e to execute the batch file
            # Use double quotes to handle spaces in the path
            cmd = f'"{self.STATA_CLI}" /e do "{batch_file}"'

            # Use Popen instead of run to enable monitoring
            proc = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                cwd=self.cwd
            )

            # Start all monitors
            if self.monitors:
                logging.info(f"Starting {len(self.monitors)} monitor(s) on Windows")
            for monitor in self.monitors:
                monitor.start(proc)

            # Wait for process to complete
            _, stderr = proc.communicate()

            # Stop all monitors (this will raise exceptions if limits were exceeded)
            if self.monitors:
                logging.info("Stopping all monitors on Windows")
            for monitor in self.monitors:
                monitor.stop()

            if proc.returncode != 0:
                logging.error(f"Stata execution failed on Windows: {stderr}")
                raise RuntimeError(f"Windows Stata execution failed: {stderr}")
            else:
                logging.info(f"Stata execution completed successfully on Windows. Log file: {log_file}")

        except RuntimeError:
            # Re-raise RuntimeError (includes monitor errors)
            raise
        except Exception as e:
            logging.error(f"Error during Windows Stata execution: {str(e)}")
            raise RuntimeError(f"Windows Stata execution error: {str(e)}")
        finally:
            # Clean up temporary batch file
            if batch_file.exists():
                try:
                    batch_file.unlink()
                    logging.debug(f"Temporary batch file removed: {batch_file}")
                except Exception as e:
                    logging.warning(f"Failed to remove temporary batch file {batch_file}: {str(e)}")

    @staticmethod
    def read_log(log_file_path, mode="r", encoding="utf-8") -> str:
        try:
            with open(log_file_path, mode, encoding=encoding) as file:
                log_content = file.read()
            return log_content
        except Exception as e:
            return f"Failed to read logfile-{log_file_path}: {e}"
