# apps/core/utils/secret_manager.py
import hashlib
import json
import os
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, Optional

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.utils import get_random_secret_key


class SecretKeyManager:
    """
    SECRET_KEY 管理器
    功能：密钥生成、持久化、轮换、验证
    设计原则：密钥一旦轮换即失效，不保留备份
    """

    # 单例实例
    _instance = None
    _lock = None  # 线程锁，防止并发操作

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # 使用线程锁确保线程安全
            import threading

            cls._lock = threading.RLock()
        return cls._instance

    def __init__(self):
        if not hasattr(self, "_initialized"):
            with self._lock:
                if not hasattr(self, "_initialized"):
                    self.base_dir = Path(settings.BASE_DIR) / "apps" / "core"
                    self.secret_file = self.base_dir / ".generated_secret"
                    self._initialized = True

                    # 确保密钥文件权限安全
                    if self.secret_file.exists():
                        os.chmod(self.secret_file, 0o600)

    def get_secret_key(self) -> str:
        """
        获取当前使用的 SECRET_KEY
        优先级：环境变量 > 密钥文件 > 默认密钥
        """
        with self._lock:
            # 1. 环境变量（最高优先级）
            env_key = os.environ.get("SECRET_KEY")
            if env_key:
                self._validate_key_strength(env_key)
                return env_key

            # 2. 密钥文件
            if self.secret_file.exists():
                file_key = self._load_from_file()
                if file_key:
                    self._validate_key_strength(file_key)
                    return file_key

            # 3. 项目特定的默认密钥
            project_hash = hashlib.sha256(str(self.base_dir).encode()).hexdigest()[:40]
            default_key = f"django-insecure-{project_hash}"

            return default_key

    def initialize_or_rotate_secret_key(self) -> Dict:
        """
        初始化或轮换密钥
        返回：包含操作结果和信息的字典
        """
        with self._lock:
            result = {
                "action": "none",
                "message": "",
                "key_age_days": 0,
                "need_restart": False,
            }

            # 如果文件不存在，初始化
            if not self.secret_file.exists():
                self._generate_and_save_secret("系统初始化")
                result.update(
                    {
                        "action": "initialized",
                        "message": "密钥文件已初始化",
                        "need_restart": True,
                    }
                )
                return result

            # 加载现有密钥信息
            key_info = self._get_key_info()
            if not key_info:
                # 文件损坏，重新生成
                self._generate_and_save_secret("文件损坏重新生成")
                result.update(
                    {
                        "action": "regenerated",
                        "message": "密钥文件损坏，已重新生成",
                        "need_restart": True,
                    }
                )
                return result

            # 计算密钥年龄
            created_at = datetime.fromisoformat(key_info["created_at"].replace("Z", "+00:00"))
            age_days = (datetime.now() - created_at).days
            result["key_age_days"] = age_days

            # 检查是否需要轮换（超过90天）
            if age_days > 90:
                self._generate_and_save_secret("自动轮换")
                result.update(
                    {
                        "action": "rotated",
                        "message": f"密钥已使用{age_days}天，已自动轮换",
                        "need_restart": True,
                    }
                )
                return result

            # 检查是否需要警告（超过80天）
            if age_days > 80:
                result.update(
                    {"action": "warning", "message": f"密钥已使用{age_days}天，建议考虑轮换"}
                )
                return result

            # 检查与当前使用的密钥是否一致
            current_key = settings.SECRET_KEY
            file_key = key_info.get("secret_key", "")

            if file_key and file_key != current_key:
                if "django-insecure-" in current_key:
                    result.update(
                        {
                            "action": "mismatch",
                            "message": "当前使用默认密钥，持久化密钥已生成但需重启生效",
                            "need_restart": True,
                        }
                    )
                else:
                    result.update(
                        {
                            "action": "mismatch",
                            "message": "当前密钥与文件不一致，可能是环境变量覆盖",
                        }
                    )

            return result

    def rotate_secret_manual(self, reason: str = "手动轮换") -> Dict:
        """
        手动轮换密钥
        """
        with self._lock:
            self._generate_and_save_secret(reason)

            return {
                "success": True,
                "message": f"密钥已{reason}",
                "need_restart": True,
            }

    def get_secret_info(self) -> Dict:
        """
        获取密钥详细信息
        """
        with self._lock:
            info = {
                "current_key_length": len(settings.SECRET_KEY),
                "using_default": "django-insecure-" in settings.SECRET_KEY,
                "from_env": "SECRET_KEY" in os.environ,
                "secret_file_exists": self.secret_file.exists(),
                "key_age_days": 0,
                "last_rotation": None,
                "rotation_reason": None,
                "status": "unknown",
            }

            if self.secret_file.exists():
                key_info = self._get_key_info()
                if key_info:
                    created_at = datetime.fromisoformat(
                        key_info["created_at"].replace("Z", "+00:00")
                    )
                    age_days = (datetime.now() - created_at).days

                    info.update(
                        {
                            "key_age_days": age_days,
                            "last_rotation": key_info.get("created_at"),
                            "rotation_reason": key_info.get("rotated_reason", "unknown"),
                            "key_hash": key_info.get("hash", ""),
                            "file_key_length": key_info.get("length", 0),
                        }
                    )

                    # 判断状态
                    if age_days > 90:
                        info["status"] = "expired"
                    elif age_days > 80:
                        info["status"] = "warning"
                    else:
                        info["status"] = "healthy"

                    # 检查一致性
                    file_key = key_info.get("secret_key", "")
                    if file_key and file_key == settings.SECRET_KEY:
                        info["consistent"] = True
                    else:
                        info["consistent"] = False

            return info

    def reset_secret_key(self) -> Dict:
        """
        重置SECRET_KEY
        返回：操作结果
        """
        with self._lock:
            try:
                # 直接生成新密钥，覆盖旧密钥
                self._generate_and_save_secret("API重置")

                return {
                    "success": True,
                    "message": "SECRET_KEY已重置",
                    "warning": "需要重启应用才能使新密钥生效",
                }

            except Exception as e:
                return {"success": False, "message": f"重置失败: {str(e)}"}

    def _generate_and_save_secret(self, reason: str) -> None:
        """生成新密钥并保存到文件（安全覆盖）"""
        new_key = get_random_secret_key()

        key_data = {
            "secret_key": new_key,
            "created_at": datetime.now().isoformat(),
            "rotated_at": datetime.now().isoformat(),
            "rotated_reason": reason,
            "length": len(new_key),
            "hash": hashlib.sha256(new_key.encode()).hexdigest()[:16],
        }

        # 使用临时文件确保写入安全
        temp_file = self.secret_file.with_suffix(".tmp")

        try:
            # 写入临时文件
            with open(temp_file, "w") as f:
                json.dump(key_data, f, indent=2, ensure_ascii=False)

            # 设置临时文件权限
            os.chmod(temp_file, 0o600)

            # 原子操作：重命名临时文件为目标文件
            # 这样要么成功要么失败，不会出现半写入状态
            temp_file.rename(self.secret_file)

        except Exception as e:
            # 如果失败，尝试清理临时文件
            if temp_file.exists():
                try:
                    temp_file.unlink()
                except:
                    pass
            raise e

        finally:
            # 确保临时文件被清理
            if temp_file.exists():
                try:
                    temp_file.unlink()
                except:
                    pass

    def _load_from_file(self) -> Optional[str]:
        """从文件加载密钥"""
        try:
            with open(self.secret_file, "r") as f:
                content = f.read().strip()

            if content.startswith("{"):
                data = json.loads(content)
                return data.get("secret_key", "").strip()
            else:
                # 简单格式：第一行是密钥
                return content.split("\n")[0].strip()

        except Exception:
            return None

    def _get_key_info(self) -> Optional[Dict]:
        """获取密钥文件中的信息"""
        try:
            with open(self.secret_file, "r") as f:
                data = json.load(f)

            if isinstance(data, dict) and "secret_key" in data:
                return data
        except Exception:
            pass
        return None

    def _validate_key_strength(self, key: str) -> None:
        """验证密钥强度"""
        if len(key) < 50:
            raise ImproperlyConfigured(f"SECRET_KEY 长度不足（{len(key)}字符）")

    def clear_secret_file(self) -> bool:
        """
        清除密钥文件（用于特殊情况下需要强制重新生成）
        谨慎使用！
        """
        with self._lock:
            try:
                if self.secret_file.exists():
                    self.secret_file.unlink()
                    return True
                return False
            except Exception:
                return False


# 创建全局实例
secret_manager = SecretKeyManager()
