# 경로/파일: modules/user.py
import os
import json
import re
import subprocess
from typing import Dict, List, Optional
from passlib.hash import sha512_crypt

# ✅ 경로만 변경
BASE_DIR = "/opt/realcom-nas"
USERS_JSON = os.path.join(BASE_DIR, "nas_users.json")

SAMBA_CONF_PATH = "/etc/samba/nas_smb.conf"

# 메모리 캐시 (UI 로그인 검증용)
user_passwords: Dict[str, str] = {}

# 기본 admin 계정 해시 (비밀번호: 1)
DEFAULT_ADMIN_HASH = "$6$rounds=656000$8Pn71BWR3cZN54ua$rCeMUKGJ651SHlPPdbw7VYEFEM0wTBAdXDAmu7Np8xRhhKiJRCuW6E4a79dJJL3VTrhRwYLzlFEp2hZ6hphW80"


# -----------------------------
# 내부 유틸
# -----------------------------
def _run(cmd: List[str], check: bool = False, input_text: Optional[str] = None) -> subprocess.CompletedProcess:
    """
    subprocess wrapper. 실패해도 앱이 죽지 않도록 check 기본 False.
    """
    try:
        return subprocess.run(
            cmd,
            input=input_text,
            text=True,
            capture_output=True,
            check=check
        )
    except Exception as e:
        # 절대 예외를 밖으로 던지지 않음(웹앱 기동 안정성)
        return subprocess.CompletedProcess(cmd, returncode=1, stdout="", stderr=str(e))


def _restart_samba():
    # restart가 제일 확실함. 실패해도 앱 죽이면 안 됨.
    _run(["sudo", "systemctl", "restart", "smbd", "nmbd"], check=False)


def _testparm_ok() -> bool:
    p = _run(["testparm", "-s"], check=False)
    if p.returncode != 0:
        print("[ERROR] testparm failed")
        if p.stderr:
            print(p.stderr.strip())
        return False
    return True


def _ensure_linux_user(username: str):
    # 이미 있으면 adduser가 에러내도 무시
    _run(["sudo", "adduser", "--disabled-password", "--gecos", '""', username], check=False)


def add_samba_user(username: str, password: str) -> bool:
    """
    Samba 사용자 추가/비번설정/활성화
    - 이미 존재해도 비번/활성화는 맞춰줌
    """
    try:
        # 추가(이미 있으면 실패할 수 있음)
        p = _run(["sudo", "smbpasswd", "-a", username], check=False, input_text=f"{password}\n{password}\n")
        if p.returncode != 0 and p.stderr:
            print(f"[WARN] smbpasswd -a stderr: {p.stderr.strip()}")

        # 비번 변경(확실히 맞추기)
        p2 = _run(["sudo", "smbpasswd", username], check=False, input_text=f"{password}\n{password}\n")
        if p2.returncode != 0:
            if p2.stderr:
                print(f"[ERROR] smbpasswd set password failed: {p2.stderr.strip()}")
            return False

        # 활성화
        p3 = _run(["sudo", "smbpasswd", "-e", username], check=False)
        if p3.returncode != 0:
            if p3.stderr:
                print(f"[ERROR] smbpasswd -e failed: {p3.stderr.strip()}")
            return False

        print(f"[INFO] Samba 사용자 '{username}' 추가/비번설정/활성화 완료")
        return True
    except Exception as e:
        print(f"[ERROR] Samba 사용자 추가 실패: {e}")
        return False


def _update_storage_valid_users_conf(valid_users: List[str]) -> bool:
    """
    /etc/samba/nas_smb.conf의 [STORAGE] 섹션 'valid users ='를 자동 갱신한다.
    - UI 사용자 목록 기반으로 valid users 갱신
    - 운영 편의를 위해 realnas은 항상 포함
    """
    try:
        users = [u.strip() for u in valid_users if u and u.strip()]
        if "realnas" not in users:
            users.append("realnas")
        users = sorted(set(users))

        if not os.path.exists(SAMBA_CONF_PATH):
            print(f"[WARN] Samba conf not found: {SAMBA_CONF_PATH}")
            return False

        with open(SAMBA_CONF_PATH, "r", encoding="utf-8") as f:
            content = f.read()

        m = re.search(r"(?ms)^\[STORAGE\]\s*$([\s\S]*?)(?=^\[|\Z)", content)
        if not m:
            print("[WARN] [STORAGE] section not found in nas_smb.conf")
            return False

        body = m.group(1)
        new_line = f"    valid users = {' '.join(users)}"

        if re.search(r"(?m)^\s*valid users\s*=", body):
            body2 = re.sub(r"(?m)^\s*valid users\s*=.*$", new_line, body)
        else:
            body2 = body.rstrip() + "\n" + new_line + "\n"

        new_block = "[STORAGE]\n" + body2.lstrip("\n")
        new_content = content[:m.start()] + new_block + content[m.end():]

        if new_content != content:
            with open(SAMBA_CONF_PATH, "w", encoding="utf-8") as f:
                f.write(new_content)

        # 문법 체크 실패하면 samba restart는 안 함
        if not _testparm_ok():
            print("[ERROR] Samba conf updated but testparm failed. not restarting samba.")
            return False

        _restart_samba()
        print(f"[INFO] Samba valid users updated: {users}")
        return True

    except Exception as e:
        print(f"[ERROR] update_storage_valid_users_conf failed: {e}")
        return False


# -----------------------------
# 사용자 DB
# -----------------------------
def load_users():
    """
    ⚠️ 중요: Flask import 시점에 호출되어도 서버가 죽지 않게 설계
    - 파일 읽기/기본계정 준비만 하고,
    - Samba 반영은 실패해도 예외 던지지 않음
    """
    global user_passwords

    if os.path.exists(USERS_JSON):
        try:
            with open(USERS_JSON, "r", encoding="utf-8") as f:
                user_passwords = json.load(f)
        except Exception as e:
            print(f"[ERROR] 사용자 데이터 로드 실패: {e}")
            user_passwords = {}
    else:
        user_passwords = {}

    # 기본 admin 계정 없으면 추가 + 시스템/삼바에도 등록(실패해도 앱은 살아야 함)
    if "admin" not in user_passwords:
        user_passwords["admin"] = DEFAULT_ADMIN_HASH
        save_users()
        try:
            _ensure_linux_user("admin")
            add_samba_user("admin", "1")
        except Exception as e:
            print(f"[WARN] 기본 admin samba/linux 세팅 실패(무시): {e}")

    # import 시점에 samba 건드리다 앱 죽는 경우가 많아서,
    # 여기서는 "시도만" 하고 실패는 무시한다.
    try:
        _update_storage_valid_users_conf(list_users())
    except Exception as e:
        print(f"[WARN] 초기 samba valid users sync 실패(무시): {e}")


def save_users() -> bool:
    try:
        os.makedirs(BASE_DIR, exist_ok=True)
        with open(USERS_JSON, "w", encoding="utf-8") as f:
            json.dump(user_passwords, f, indent=2, ensure_ascii=False)
        return True
    except Exception as e:
        print(f"[ERROR] 사용자 데이터 저장 실패: {e}")
        return False


def verify_password(username: str, password: str) -> bool:
    hashed = user_passwords.get(username)
    if not hashed:
        return False
    try:
        return sha512_crypt.verify(password, hashed)
    except Exception:
        return False


# -----------------------------
# 사용자 관리 (UI에서 호출)
# -----------------------------
def add_user(username: str, password: str) -> bool:
    global user_passwords

    username = (username or "").strip()
    password = (password or "").strip()

    if not username or not password:
        return False

    if username in user_passwords:
        print(f"[WARN] 사용자 '{username}' 이미 존재")
        return False

    try:
        # 1) JSON DB 저장
        hashed = sha512_crypt.hash(password)
        user_passwords[username] = hashed
        if not save_users():
            user_passwords.pop(username, None)
            return False

        # 2) 리눅스 유저 생성
        _ensure_linux_user(username)

        # 3) Samba 유저 추가/비번/활성화
        if not add_samba_user(username, password):
            print("[WARN] Samba 등록 실패로 사용자 롤백 시도")
            user_passwords.pop(username, None)
            save_users()
            return False

        # 4) Samba 공유 valid users 자동 반영 + 재시작
        _update_storage_valid_users_conf(list_users())

        return True

    except Exception as e:
        print(f"[ERROR] 사용자 추가 실패: {e}")
        return False


def change_password(username: str, new_password: str) -> bool:
    username = (username or "").strip()
    new_password = (new_password or "").strip()

    if not username or not new_password:
        return False

    if username not in user_passwords:
        print(f"[WARN] 사용자 '{username}' 존재하지 않음")
        return False

    try:
        # 1) JSON DB 갱신
        hashed = sha512_crypt.hash(new_password)
        user_passwords[username] = hashed
        if not save_users():
            return False

        # 2) Samba 비번 변경
        p = _run(["sudo", "smbpasswd", username], check=False, input_text=f"{new_password}\n{new_password}\n")
        if p.returncode != 0:
            print(f"[ERROR] Samba 비밀번호 변경 실패: {(p.stderr or '').strip()}")
            return False

        print(f"[INFO] 사용자 '{username}' 비밀번호 변경 완료")
        return True

    except Exception as e:
        print(f"[ERROR] 비밀번호 변경 예외: {e}")
        return False


def remove_user(username: str) -> bool:
    global user_passwords

    username = (username or "").strip()
    if not username:
        return False

    # 운영 핵심 계정 보호
    if username == "realnas":
        print("[WARN] 'realnas' 계정은 삭제할 수 없음")
        return False

    if username not in user_passwords:
        print(f"[WARN] 사용자 '{username}' 존재하지 않음")
        return False

    try:
        # 1) JSON DB에서 제거
        old_hash = user_passwords.get(username)
        user_passwords.pop(username, None)
        if not save_users():
            if old_hash:
                user_passwords[username] = old_hash
            return False

        # 2) Samba 사용자 삭제
        _run(["sudo", "smbpasswd", "-x", username], check=False)

        # 3) 시스템 사용자 삭제(선택적)
        _run(["sudo", "deluser", "--remove-home", username], check=False)

        # 4) Samba 공유 valid users 자동 반영 + 재시작
        _update_storage_valid_users_conf(list_users())

        print(f"[INFO] 사용자 '{username}' 삭제 완료")
        return True

    except Exception as e:
        print(f"[ERROR] 사용자 삭제 실패: {e}")
        return False


def list_users() -> list:
    return list(user_passwords.keys())


# 모듈 로드 시 사용자 데이터 미리 불러오기
# (실패해도 앱이 죽지 않게 load_users 내부에서 다 잡는다)
load_users()
