from infisical_sdk import InfisicalSDKClient
from requests.exceptions import InvalidURL
import os, sys
from pathlib import Path
import time


class InfisicalManager:
    """
    Full Infisical bootstrapper:
      - Environment validation
      - Client initialisation
      - Secret fetching
      - Single secret lookup
    All with clean CLI output and fatal exits on error.
    """

    REQUIRED_KEYS = ["INFISICAL_HOST", "INFISICAL_CLIENT_ID", "INFISICAL_CLIENT_SECRET"]

    def __init__(self, dotenv_path=".env", debug=False):
        self.dotenv_path = dotenv_path
        self.env = None
        self.client = None
        self.debug = debug

    # ------------------------------------------------------------
    # INTERNAL DEBUG LOGGER
    # ------------------------------------------------------------
    def _log(self, msg):
        if self.debug:
            print(msg)

    # ------------------------------------------------------------
    # ENVIRONMENT VALIDATION
    # ------------------------------------------------------------
    def check_env(self):
        t0 = time.time()

        def load_dotenv(path):
            if not Path(path).exists():
                return
            with open(path) as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#") or "=" not in line:
                        continue
                    key, value = line.split("=", 1)
                    os.environ.setdefault(key.strip(), value.strip())

        missing = [k for k in self.REQUIRED_KEYS if k not in os.environ]

        if missing:
            self._log("⚠️ Missing variables in environment, checking .env…")
            load_dotenv(self.dotenv_path)
            missing = [k for k in self.REQUIRED_KEYS if k not in os.environ]

        if missing:
            print("❌ Missing required environment variables:")
            for key in missing:
                print(f"   - {key} (not found in environment or .env)")
            print("")
            print("Save the following at /etc/environment or in .env\n")
            print('INFISICAL_HOST="http://de-platform.taild8d2e.ts.net:8091"')
            print('INFISICAL_CLIENT_ID="<>"')
            print('INFISICAL_CLIENT_SECRET="<>"')
            print("(You can get these values from your Infisical dashboard)\n")
            sys.exit(1)

        t1 = time.time()
        self._log(f"✅ Env check completed in {(t1 - t0) * 1000:.2f} ms")
        self._log("✅ All required environment variables are present.")

        self.env = {k: os.environ.get(k) for k in self.REQUIRED_KEYS}
        return self.env

    # ------------------------------------------------------------
    # CLIENT INITIALISATION
    # ------------------------------------------------------------
    def init_client(self):
        self._log("⚙️  Initialising client…")

        if self.env is None:
            self.check_env()

        try:
            client = InfisicalSDKClient(host=self.env["INFISICAL_HOST"])
            client.auth.universal_auth.login(
                client_id=self.env["INFISICAL_CLIENT_ID"],
                client_secret=self.env["INFISICAL_CLIENT_SECRET"]
            )
            self.client = client
            self._log("✅ Client initialised successfully.")
            return client

        except InvalidURL as exc:
            print("❌ Invalid Infisical host URL.")
            print(f"   Host: {self.env['INFISICAL_HOST']}")
            print(f"   Reason: {exc}")
            sys.exit(1)

        except Exception as exc:
            message = str(exc).lower()

            # Connection issues
            if any(term in message for term in [
                "connection", "timeout", "refused", "unreachable",
                "failed to establish", "failed to connect",
                "cannot connect", "name or service not known",
                "getaddrinfo", "dns", "max retries",
                "newconnectionerror", "clientconnectorerror"
            ]):
                print("❌ Failed to connect to Infisical host.")
                print(f"   Host: {self.env['INFISICAL_HOST']}")
                print(f"   Reason: {exc}")
                sys.exit(1)

            # Authentication issues
            if any(term in message for term in [
                "unauthorized", "invalid", "authentication", "401", "forbidden"
            ]):
                print("❌ Authentication failed.")
                print("   The client ID or client secret appears to be incorrect.")
                print(f"   Reason: {exc}")
                sys.exit(1)

            print("❌ Unexpected error while initialising Infisical client.")
            print(f"   Reason: {exc}")
            sys.exit(1)

    # ------------------------------------------------------------
    # SECRET FETCHING
    # ------------------------------------------------------------
    def fetch_secrets(self, project, environment="prod", path="/"):
        self._log("🔐 Fetching secrets…")

        if self.client is None:
            print("❌ Cannot fetch secrets: client is not initialised.")
            sys.exit(1)

        try:
            response = self.client.secrets.list_secrets(
                project_slug=project,
                environment_slug=environment,
                secret_path=path
            )

            secrets = {item.secretKey: item.secretValue for item in response.secrets}
            self._log(f"✅ Retrieved {len(secrets)} secrets.")
            return secrets

        except Exception as exc:
            message = str(exc).lower()

            # ENVIRONMENT NOT FOUND
            if any(term in message for term in [
                "environment '", "ensure the environment slug",
                "in environment", "environment", "was not found",
                "status: 404"
            ]):
                print("❌ Environment slug not found.")
                print(f"   Environment slug: {environment}")
                print(f"   Reason: {exc}")
                sys.exit(1)

            # PROJECT NOT FOUND
            if any(term in message for term in [
                "project '", "projectslug", "invalid project",
                "project", "does not exist", "was not found",
                "status: 404"
            ]):
                print("❌ Project slug not found.")
                print(f"   Project slug: {project}")
                print(f"   Reason: {exc}")
                sys.exit(1)

            # PATH NOT FOUND
            if any(term in message for term in [
                "path", "secret path", "does not exist", "not found"
            ]):
                print("❌ Path not found.")
                print(f"   Path: {path}")
                print(f"   Reason: {exc}")
                sys.exit(1)

            # AUTHENTICATION
            if any(term in message for term in ["unauthorized", "forbidden", "401"]):
                print("❌ Authentication failed while fetching secrets.")
                print("   Your token may be invalid or expired.")
                print(f"   Reason: {exc}")
                sys.exit(1)

            # NETWORK
            if any(term in message for term in [
                "connection", "timeout", "refused", "unreachable",
                "failed to establish", "failed to connect",
                "cannot connect", "name or service not known",
                "getaddrinfo", "dns", "max retries"
            ]):
                print("❌ Network error while fetching secrets.")
                print(f"   Reason: {exc}")
                sys.exit(1)

            print("❌ Unexpected error while fetching secrets.")
            print(f"   Reason: {exc}")
            sys.exit(1)

    # ------------------------------------------------------------
    # SINGLE SECRET LOOKUP
    # ------------------------------------------------------------
    def get_secret(self, project, environment="prod", key=None, path="/"):
        if key is None:
            print("❌ get_secret() requires a key.")
            sys.exit(1)

        self._log(f"🔎 Fetching secret: {key}")

        secrets = self.fetch_secrets(project, environment, path)

        if key not in secrets:
            print("❌ Secret key not found.")
            print(f"   Key: {key}")
            print(f"   Project: {project}")
            print(f"   Environment: {environment}")
            sys.exit(1)

        self._log("✅ Secret retrieved successfully.")
        return secrets[key]


# ------------------------------------------------------------
# MAIN (example usage)
# ------------------------------------------------------------
if __name__ == "__main__":
    mgr = InfisicalManager(debug=True) #debug=True
    client = mgr.init_client()
    secrets = mgr.fetch_secrets("cloudflare-jp-wd")

    print(f"\n🔑 Secrets dictionary:\n {secrets}")

    children = mgr.get_secret("cloudflare-jp-wd", "prod", "zone-children")
    print("Children:", children)