"""SalmAlm Doctor — self-diagnosis and repair tool.

자가 진단 + 수복 도구.
"""

from __future__ import annotations

import json
import os
import socket
import sqlite3
import shutil
import time
from datetime import timezone, timedelta
from pathlib import Path
from typing import List

KST = timezone(timedelta(hours=9))

_SALMALM_DIR = Path.home() / ".salmalm"


def _status(ok: bool, msg: str, fixable: bool = False, issue_id: str = "") -> dict:
    return {
        "status": "ok" if ok else "issue",
        "message": msg,
        "fixable": fixable,
        "issue_id": issue_id,
    }


class Doctor:
    """SalmAlm 자가 진단 + 수복 도구."""

    def run_all(self, auto_fix: bool = False) -> List[dict]:
        """전체 진단 실행, 결과 리스트 반환."""
        checks = [
            self.check_config_integrity,
            self.check_database_integrity,
            self.check_session_integrity,
            self.check_api_keys,
            self.check_port_availability,
            self.check_disk_space,
            self.check_permissions,
            self.check_oauth_expiry,
        ]
        results = []
        for check in checks:
            try:
                result = check()
            except Exception as e:
                result = _status(False, f"{check.__name__}: {e}")
            results.append(result)
            if auto_fix and result.get("fixable") and result.get("issue_id"):
                try:
                    self.repair(result["issue_id"])
                    result["auto_fixed"] = True
                except Exception:
                    result["auto_fixed"] = False
        return results

    def check_config_integrity(self) -> dict:
        """설정 파일 존재/유효성/권한 검사."""
        if not _SALMALM_DIR.exists():
            return _status(False, "~/.salmalm directory missing", fixable=True, issue_id="missing_dir")
        config_files = list(_SALMALM_DIR.glob("*.json"))
        bad = []
        for cf in config_files:
            try:
                json.loads(cf.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError) as e:
                bad.append(f"{cf.name}: {e}")
        if bad:
            return _status(False, f"Invalid config files: {', '.join(bad)}", fixable=False, issue_id="bad_config")
        return _status(True, f"{len(config_files)} config files OK")

    def check_database_integrity(self) -> dict:
        """SQLite DB 무결성 (PRAGMA integrity_check)."""
        db_files = list(_SALMALM_DIR.glob("*.db")) + list(_SALMALM_DIR.glob("*.sqlite"))
        if not db_files:
            return _status(True, "No databases found")
        bad = []
        for db in db_files:
            try:
                conn = sqlite3.connect(str(db))
                result = conn.execute("PRAGMA integrity_check").fetchone()
                conn.close()
                if result[0] != "ok":
                    bad.append(f"{db.name}: {result[0]}")
            except Exception as e:
                bad.append(f"{db.name}: {e}")
        if bad:
            return _status(False, f"DB issues: {'; '.join(bad)}")
        return _status(True, f"{len(db_files)} databases OK")

    def check_session_integrity(self) -> dict:
        """세션 파일 존재/크기/손상 검사."""
        session_dir = _SALMALM_DIR / "sessions"
        if not session_dir.exists():
            return _status(True, "No session directory (OK)")
        files = list(session_dir.iterdir())
        bad = []
        for f in files:
            if f.suffix == ".json":
                try:
                    data = json.loads(f.read_text(encoding="utf-8"))
                    if not isinstance(data, dict):
                        bad.append(f.name)
                except Exception:
                    bad.append(f.name)
            if f.stat().st_size == 0:
                bad.append(f"{f.name} (empty)")
        if bad:
            return _status(False, f"Bad sessions: {', '.join(bad)}", fixable=True, issue_id="bad_sessions")
        return _status(True, f"{len(files)} session files OK")

    def check_api_keys(self) -> dict:
        """API 키 설정 여부."""
        try:
            from salmalm.security.crypto import vault

            if not vault.is_unlocked:
                return _status(False, "Vault is locked", fixable=False)
            keys = ["anthropic_api_key"]
            missing = [k for k in keys if not vault.get(k)]
            if missing:
                return _status(False, f"Missing keys: {', '.join(missing)}")
            return _status(True, "API keys present")
        except Exception as e:
            return _status(False, f"Cannot check keys: {e}")

    def check_port_availability(self) -> dict:
        """서버 포트 사용 가능 여부."""
        port = int(os.environ.get("SALMALM_PORT", "8080"))
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(1)
            result = s.connect_ex(("127.0.0.1", port))
            s.close()
            if result == 0:
                return _status(True, f"Port {port} is in use (server running)")
            return _status(True, f"Port {port} is available")
        except Exception as e:
            return _status(False, f"Port check failed: {e}")

    def check_disk_space(self) -> dict:
        """디스크 여유 공간."""
        usage = shutil.disk_usage(str(Path.home()))
        free_gb = usage.free / (1024**3)
        if free_gb < 1.0:
            return _status(False, f"Low disk: {free_gb:.1f}GB free", fixable=False)
        return _status(True, f"Disk OK: {free_gb:.1f}GB free")

    def check_permissions(self) -> dict:
        """설정 파일 권한 (600 권장)."""
        if not _SALMALM_DIR.exists():
            return _status(True, "No config directory")
        issues = []
        for f in _SALMALM_DIR.glob("*.json"):
            mode = f.stat().st_mode & 0o777
            if mode & 0o077:  # group/other can read
                issues.append(f"{f.name}: {oct(mode)}")
        if issues:
            return _status(False, f"Loose permissions: {', '.join(issues)}", fixable=True, issue_id="permissions")
        return _status(True, "File permissions OK")

    def check_oauth_expiry(self) -> dict:
        """OAuth 토큰 만료 임박 여부."""
        token_file = _SALMALM_DIR / "oauth_tokens.json"
        if not token_file.exists():
            return _status(True, "No OAuth tokens")
        try:
            tokens = json.loads(token_file.read_text(encoding="utf-8"))
            now = time.time()
            expiring = []
            for name, tok in tokens.items():
                exp = tok.get("expires_at", 0)
                if exp and exp - now < 3600:
                    expiring.append(name)
            if expiring:
                return _status(False, f"Tokens expiring soon: {', '.join(expiring)}", fixable=False)
            return _status(True, "OAuth tokens OK")
        except Exception as e:
            return _status(False, f"Cannot read tokens: {e}")

    def migrate_config(self) -> dict:
        """구 설정 → 신 설정 자동 마이그레이션."""
        try:
            from salmalm.config_manager import ConfigManager

            migrated = ConfigManager.migrate("channels")
            return _status(True, f"Migration {'applied' if migrated else 'not needed'}")
        except Exception as e:
            return _status(False, f"Migration failed: {e}")

    def repair(self, issue_id: str) -> bool:
        """특정 이슈 자동 수복."""
        if issue_id == "missing_dir":
            _SALMALM_DIR.mkdir(parents=True, exist_ok=True)
            return True
        elif issue_id == "permissions":
            for f in _SALMALM_DIR.glob("*.json"):
                f.chmod(0o600)
            return True
        elif issue_id == "bad_sessions":
            session_dir = _SALMALM_DIR / "sessions"
            if session_dir.exists():
                for f in session_dir.iterdir():
                    if f.stat().st_size == 0:
                        f.unlink()
            return True
        return False

    def format_report(self, results: List[dict] = None) -> str:
        """Format diagnosis results as human-readable text."""
        if results is None:
            results = self.run_all()
        lines = ["🏥 **SalmAlm Doctor Report**\n"]
        for r in results:
            icon = "✅" if r["status"] == "ok" else "❌"
            fix = " (🔧 fixable)" if r.get("fixable") else ""
            fixed = " ✨ auto-fixed" if r.get("auto_fixed") else ""
            lines.append(f"{icon} {r['message']}{fix}{fixed}")
        ok = sum(1 for r in results if r["status"] == "ok")
        total = len(results)
        lines.append(f"\n📊 {ok}/{total} checks passed")
        return "\n".join(lines)


# Singleton
doctor = Doctor()
