from __future__ import annotations

import os
import subprocess
import tempfile
from dataclasses import dataclass

from .config import Config


@dataclass
class IpaResult:
    ok: bool
    message: str


def kinit(config: Config) -> None:
    if config.ipa_principal and config.ipa_keytab:
        subprocess.run(
            ["kinit", "-kt", config.ipa_keytab, config.ipa_principal],
            check=True,
            text=True,
            capture_output=True,
        )


def create_user(config: Config, user: dict[str, str | None]) -> IpaResult:
    username = user["username"] or ""
    email = user["email"] or ""
    full_name = user["full_name"] or username
    parts = full_name.split(" ", 1)
    first_name = parts[0]
    last_name = parts[1] if len(parts) > 1 else username

    if config.ipa_mode != "cli":
        return IpaResult(True, f"dry-run: would create IPA user {username} <{email}>")

    kinit(config)

    add_cmd = [
        "ipa",
        "user-add",
        username,
        "--first",
        first_name,
        "--last",
        last_name,
        "--cn",
        full_name,
        "--email",
        email,
        "--shell",
        config.ipa_default_shell,
        "--homedir",
        f"{config.ipa_home_root.rstrip('/')}/{username}",
        "--random",
        "--user-auth-type",
        "password",
    ]
    try:
        subprocess.run(add_cmd, check=True, text=True, capture_output=True)
    except subprocess.CalledProcessError as exc:
        return IpaResult(False, exc.stderr.strip() or exc.stdout.strip() or str(exc))
    return IpaResult(True, f"created IPA user {username}")


def add_default_group(config: Config, username: str) -> IpaResult:
    if config.ipa_mode != "cli":
        return IpaResult(True, f"dry-run: would add {username} to {config.default_ipa_group}")
    try:
        kinit(config)
        subprocess.run(
            ["ipa", "group-add-member", config.default_ipa_group, "--users", username],
            check=True,
            text=True,
            capture_output=True,
        )
    except subprocess.CalledProcessError as exc:
        return IpaResult(False, exc.stderr.strip() or exc.stdout.strip() or str(exc))
    return IpaResult(True, f"added {username} to {config.default_ipa_group}")


def set_user_enabled(config: Config, username: str, enabled: bool) -> IpaResult:
    action = "enable" if enabled else "disable"
    if config.ipa_mode != "cli":
        return IpaResult(True, f"dry-run: would {action} IPA user {username}")
    try:
        kinit(config)
        subprocess.run(
            ["ipa", f"user-{action}", username],
            check=True,
            text=True,
            capture_output=True,
        )
    except subprocess.CalledProcessError as exc:
        return IpaResult(False, exc.stderr.strip() or exc.stdout.strip() or str(exc))
    return IpaResult(True, f"{action}d IPA user {username}")


def provision_user(config: Config, user: dict[str, str | None], *, active: bool = True) -> IpaResult:
    username = user["username"] or ""
    created = create_user(config, user)
    if not created.ok:
        return created

    messages = [created.message]
    if active:
        grouped = add_default_group(config, username)
        messages.append(grouped.message)
        if not grouped.ok:
            return IpaResult(False, "; ".join(messages))
    else:
        disabled = set_user_enabled(config, username, False)
        messages.append(disabled.message)
        if not disabled.ok:
            return IpaResult(False, "; ".join(messages))
    return IpaResult(True, "; ".join(messages))


def approve_user(config: Config, username: str) -> IpaResult:
    enabled = set_user_enabled(config, username, True)
    if not enabled.ok:
        return enabled
    grouped = add_default_group(config, username)
    if not grouped.ok:
        return IpaResult(False, f"{enabled.message}; {grouped.message}")
    return IpaResult(True, f"{enabled.message}; {grouped.message}")


def set_password(config: Config, username: str, password: str) -> IpaResult:
    if config.ipa_mode != "cli":
        return IpaResult(True, f"dry-run: would set IPA password for {username}")
    try:
        kinit(config)
        subprocess.run(
            ["ipa", "passwd", username],
            input=f"{password}\n{password}\n",
            check=True,
            text=True,
            capture_output=True,
        )
    except subprocess.CalledProcessError as exc:
        return IpaResult(False, exc.stderr.strip() or exc.stdout.strip() or str(exc))
    return IpaResult(True, f"set IPA password for {username}")


def email_matches(config: Config, username: str, email: str) -> bool:
    try:
        kinit(config)
        result = subprocess.run(
            ["ipa", "user-show", username],
            check=True,
            text=True,
            capture_output=True,
        )
    except subprocess.CalledProcessError:
        return False
    expected = f"email address: {email.lower()}"
    return expected in result.stdout.lower()


def verify_password(config: Config, username: str, password: str) -> IpaResult:
    realm = config.ipa_principal.rsplit("@", 1)[-1] if "@" in config.ipa_principal else "POTTERNET.LAN"
    principal = f"{username}@{realm}"
    with tempfile.NamedTemporaryFile(prefix="charon-krb5cc-") as cache:
        env = os.environ.copy()
        env["KRB5CCNAME"] = cache.name
        try:
            subprocess.run(
                ["kinit", principal],
                input=f"{password}\n",
                check=True,
                text=True,
                capture_output=True,
                env=env,
            )
        except subprocess.CalledProcessError as exc:
            return IpaResult(False, exc.stderr.strip() or exc.stdout.strip() or "old password did not authenticate")
        finally:
            subprocess.run(["kdestroy", "-c", cache.name], text=True, capture_output=True, env=env)
    return IpaResult(True, f"verified password for {username}")
