import logging
import shutil
import socket
import subprocess
import sys
import threading
import time
from pathlib import Path

logger = logging.getLogger(__name__)


class EmbeddedDBManager:
    """
    [AI_DOC]
    Windows ortaminda tasinabilir (Portable) MariaDB sunucusunu baslatan ve yoneten sinif.
    Sistemde MySQL kurulu olmasa bile kullanicinin program klasorundeki
    mariadb binary'lerini ayaga kaldirir.
    [/AI_DOC]
    """

    def __init__(self, base_path: str = None):
        if base_path is None:
            self.base_path = self._get_runtime_base_path()
        else:
            self.base_path = Path(base_path)

        self.mariadb_dir = self.base_path / "mariadb"
        self.port = 3308
        self.process = None
        self._stderr_thread = None
        self._refresh_runtime_paths()

    def _get_runtime_base_path(self) -> Path:
        entry_path = str(sys.argv[0] or "").strip()
        if entry_path:
            try:
                return Path(entry_path).resolve().parent
            except Exception:
                pass
        return Path(sys.executable).resolve().parent

    def _pick_existing_path(self, *names: str) -> Path:
        for name in names:
            candidate = self.bin_dir / name
            if candidate.exists():
                return candidate
        return self.bin_dir / names[0]

    def _refresh_runtime_paths(self) -> None:
        self.bin_dir = self.mariadb_dir / "bin"
        self.data_dir = self.mariadb_dir / "data"
        self.share_dir = self.mariadb_dir / "share"
        self.plugin_dir = self.mariadb_dir / "lib" / "plugin"
        self.mysqld_exe = self._pick_existing_path("mariadbd.exe", "mysqld.exe")
        self.install_db_exe = self._pick_existing_path("mariadb-install-db.exe", "mysql_install_db.exe")

    def _runtime_layout_ready(self, base_dir: Path) -> bool:
        bin_dir = base_dir / "bin"
        return (
            ((bin_dir / "mariadbd.exe").exists() or (bin_dir / "mysqld.exe").exists())
            and (base_dir / "share" / "english" / "errmsg.sys").exists()
            and (base_dir / "lib" / "plugin").exists()
        )

    def _iter_bundle_candidates(self):
        candidates = [self.base_path / "mariadb"]
        meipass_dir = getattr(sys, "_MEIPASS", None)
        if meipass_dir:
            candidates.append(Path(meipass_dir) / "mariadb")
        candidates.extend(
            [
                Path(sys.executable).resolve().parent / "mariadb",
                Path(__file__).resolve().parent.parent / "mariadb",
                Path.cwd() / "mariadb",
            ]
        )
        seen = set()
        for candidate in candidates:
            try:
                resolved = candidate.resolve()
            except Exception:
                resolved = candidate
            key = str(resolved).lower()
            if key in seen:
                continue
            seen.add(key)
            yield resolved

    def _copy_runtime_layout(self, source_dir: Path) -> None:
        self.mariadb_dir.mkdir(parents=True, exist_ok=True)
        for child_name in ("bin", "lib", "share"):
            src = source_dir / child_name
            if not src.exists():
                continue
            shutil.copytree(src, self.mariadb_dir / child_name, dirs_exist_ok=True)

    def _ensure_runtime_layout(self) -> None:
        if self._runtime_layout_ready(self.mariadb_dir):
            self._refresh_runtime_paths()
            return

        for candidate in self._iter_bundle_candidates():
            if not candidate.exists() or not self._runtime_layout_ready(candidate):
                continue
            logger.info(f"Gomulu MariaDB dosyalari paket icinden kopyalaniyor: {candidate}")
            self._copy_runtime_layout(candidate)
            self._refresh_runtime_paths()
            if self._runtime_layout_ready(self.mariadb_dir):
                return

        raise FileNotFoundError(
            "Gomulu MariaDB runtime dosyalari eksik. "
            "Derleme ciktisinin yaninda 'mariadb' klasoru bulunmali "
            "(bin/lib/share alt klasorleri ile birlikte)."
        )

    def _drain_stderr(self) -> None:
        """MariaDB stderr kanalini tuketerek pipe buffer'inin dolmasini engeller."""
        proc = self.process
        if not proc or not proc.stderr:
            return
        try:
            for line in proc.stderr:
                if not line:
                    break
                logger.debug(f"MariaDB: {line.rstrip()}")
        except Exception:
            pass

    def is_initialized(self) -> bool:
        """Veritabani data klasoru daha onceden olusturulmus mu?"""
        return (self.data_dir / "mysql").exists()

    def initialize_db(self) -> None:
        """Ilk kurulumda veritabani semasini ve tablolari yaratir."""
        self._ensure_runtime_layout()
        if self.is_initialized():
            return

        logger.info("MariaDB data klasoru olusturuluyor...")
        if not self.data_dir.exists():
            self.data_dir.mkdir(parents=True)

        cmd = [
            str(self.install_db_exe),
            f"--datadir={self.data_dir}",
            "--password=root",
        ]

        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

        try:
            logger.info("Veritabani baslangic dosyalari hazirlaniyor (Bu biraz surebilir)...")
            subprocess.run(cmd, check=True, startupinfo=startupinfo, cwd=str(self.mariadb_dir))
            logger.info("MariaDB kurulumu basariyla tamamlandi.")
        except subprocess.CalledProcessError as e:
            logger.error(f"Veritabani kurulum hatasi: {e}")
            raise RuntimeError(f"MariaDB initialize edilemedi: {e}")

    def start(self) -> None:
        """Gomulu veritabani surecini baslatir."""
        self._ensure_runtime_layout()
        if not self.is_initialized():
            self.initialize_db()

        logger.info(f"MariaDB sunucusu baslatiliyor (Port: {self.port})...")
        cmd = [
            str(self.mysqld_exe),
            "--no-defaults",
            "--console",
            f"--basedir={self.mariadb_dir}",
            f"--datadir={self.data_dir}",
            f"--lc-messages-dir={self.share_dir}",
            f"--plugin-dir={self.plugin_dir}",
            f"--port={self.port}",
            "--bind-address=127.0.0.1",
            "--enable-named-pipe",  # Windows socket pipe bypass
        ]

        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

        self.process = subprocess.Popen(
            cmd,
            startupinfo=startupinfo,
            cwd=str(self.mariadb_dir),
            stdout=subprocess.DEVNULL,  # Ciktilari gizle
            stderr=subprocess.PIPE,  # Hatalari loglamak icin yakala
            text=True,
        )

        if self._wait_for_ready(timeout=30):
            logger.info("MariaDB basariyla ayaga kalkti ve baglantilari kabul ediyor.")
            try:
                if self.process and self.process.stderr:
                    self._stderr_thread = threading.Thread(target=self._drain_stderr, daemon=True)
                    self._stderr_thread.start()
            except Exception:
                pass
        else:
            error_output = self.process.stderr.read() if self.process and self.process.stderr else "No output"
            logger.error(f"MARIADB BASLATMA HATASI:\n{error_output}")
            self.stop()
            raise ConnectionError(f"MariaDB baslatilamadi. Detay:\n{error_output}")

    def _wait_for_ready(self, timeout: int = 30) -> bool:
        """Belirtilen porta baglanarak veritabaninin hazir olup olmadigini kontrol eder."""
        start_time = time.time()
        while time.time() - start_time < timeout:
            if self.process and self.process.poll() is not None:
                # Surec kapanmissa beklemenin anlami yok
                return False

            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            result = sock.connect_ex(("127.0.0.1", self.port))
            sock.close()

            if result == 0:
                return True
            time.sleep(0.5)

        logger.error("MariaDB baslatilamadi (Timeout). Port dinlenmiyor.")
        return False

    def stop(self) -> None:
        """Gomulu veritabanini guvenli bir sekilde kapatir."""
        if self.process:
            logger.info("MariaDB sunucusu durduruluyor...")
            try:
                admin_exe = None
                for candidate in ("mysqladmin.exe", "mariadb-admin.exe"):
                    p = self.bin_dir / candidate
                    if p.exists():
                        admin_exe = p
                        break

                if admin_exe:
                    cmd = [
                        str(admin_exe),
                        "--host=127.0.0.1",
                        f"--port={self.port}",
                        "-u",
                        "root",
                        "-proot",
                        "shutdown",
                    ]
                    startupinfo = subprocess.STARTUPINFO()
                    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                    subprocess.run(cmd, startupinfo=startupinfo, cwd=str(self.mariadb_dir), timeout=10)
            except Exception as e:
                logger.warning(f"MariaDB graceful shutdown basarisiz, zorla kapatilacak: {e}")

            try:
                if self.process.poll() is None:
                    self.process.terminate()
                    try:
                        self.process.wait(timeout=5)
                    except subprocess.TimeoutExpired:
                        self.process.kill()
            except Exception:
                pass

            self.process = None
            try:
                if self._stderr_thread and self._stderr_thread.is_alive():
                    self._stderr_thread.join(timeout=1)
            except Exception:
                pass

            self._stderr_thread = None
            logger.info("MariaDB tamamen durduruldu.")




