#!/usr/bin/env python3
"""
HAVEN GRAZURI - Production 500 Error Diagnostic Script
Run this on the server: python diagnose_500.py
It will tell you exactly what is broken.
"""

import os
import sys
import traceback
from datetime import datetime

# ── Colour helpers (degrade gracefully if terminal doesn't support them) ──────
try:
    OK   = "\033[92m[OK]    \033[0m"
    FAIL = "\033[91m[FAIL]  \033[0m"
    WARN = "\033[93m[WARN]  \033[0m"
    INFO = "\033[94m[INFO]  \033[0m"
    HEAD = "\033[1m"
    END  = "\033[0m"
except Exception:
    OK = "[OK]    "; FAIL = "[FAIL]  "; WARN = "[WARN]  "; INFO = "[INFO]  "
    HEAD = ""; END = ""

results = []

def check(label, fn):
    """Run a check function, record pass/fail/warn."""
    try:
        status, detail = fn()
        tag = OK if status == "ok" else (WARN if status == "warn" else FAIL)
        line = f"{tag}{label}"
        if detail:
            line += f"\n        → {detail}"
        print(line)
        results.append((status, label, detail))
    except Exception as e:
        tb = traceback.format_exc()
        print(f"{FAIL}{label}\n        → EXCEPTION: {e}\n{tb}")
        results.append(("fail", label, str(e)))

# ─────────────────────────────────────────────────────────────────────────────
print(f"\n{HEAD}{'='*65}{END}")
print(f"{HEAD}  HAVEN GRAZURI — 500 Diagnostic  {datetime.now():%Y-%m-%d %H:%M:%S}{END}")
print(f"{HEAD}{'='*65}{END}\n")

# ── 1. ENVIRONMENT ────────────────────────────────────────────────────────────
print(f"{HEAD}[1] Environment{END}")

def chk_python():
    v = sys.version_info
    if v >= (3, 8):
        return "ok", f"Python {v.major}.{v.minor}.{v.micro}"
    return "fail", f"Python {v.major}.{v.minor} — need 3.8+"

def chk_cwd():
    cwd = os.getcwd()
    has_manage = os.path.exists(os.path.join(cwd, "manage.py"))
    if has_manage:
        return "ok", f"CWD={cwd}, manage.py found"
    return "fail", f"CWD={cwd} — manage.py NOT found. Run from project root."

def chk_env_file():
    env_path = os.path.join(os.getcwd(), ".env")
    if not os.path.exists(env_path):
        return "fail", ".env file missing"
    with open(env_path, encoding="utf-8") as f:
        content = f.read()
    keys = [l.split("=")[0].strip() for l in content.splitlines()
            if l.strip() and not l.startswith("#") and "=" in l]
    required = ["DB_NAME", "DB_USER", "DB_PASSWORD", "DB_HOST"]
    missing = [k for k in required if k not in keys]
    if missing:
        return "fail", f".env missing keys: {missing}"
    return "ok", f".env found with keys: {keys}"

def chk_settings_module():
    sm = os.environ.get("DJANGO_SETTINGS_MODULE", "")
    if not sm:
        return "warn", "DJANGO_SETTINGS_MODULE not set in environment (passenger_wsgi.py sets it at runtime)"
    return "ok", f"DJANGO_SETTINGS_MODULE={sm}"

def chk_passenger_wsgi():
    path = os.path.join(os.getcwd(), "passenger_wsgi.py")
    if not os.path.exists(path):
        return "fail", "passenger_wsgi.py missing"
    with open(path, encoding="utf-8") as f:
        content = f.read()
    if "settings_production" not in content:
        return "warn", "passenger_wsgi.py does not reference settings_production"
    return "ok", "passenger_wsgi.py present and references settings_production"

check("Python version", chk_python)
check("Working directory / manage.py", chk_cwd)
check(".env file", chk_env_file)
check("DJANGO_SETTINGS_MODULE env var", chk_settings_module)
check("passenger_wsgi.py", chk_passenger_wsgi)

# ── 2. DOTENV + SETTINGS IMPORT ───────────────────────────────────────────────
print(f"\n{HEAD}[2] Settings & Django bootstrap{END}")

def chk_dotenv():
    try:
        from dotenv import load_dotenv
        load_dotenv(os.path.join(os.getcwd(), ".env"), override=True)
        return "ok", "python-dotenv loaded .env"
    except ImportError:
        return "fail", "python-dotenv not installed — run: pip install python-dotenv"

def chk_django_import():
    try:
        import django
        return "ok", f"Django {django.__version__} importable"
    except ImportError as e:
        return "fail", str(e)

def chk_settings_import():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "branch_system.settings_production")
    try:
        from django.conf import settings as s
        if not s.configured:
            import django
            django.setup()
        _ = s.DATABASES
        return "ok", "settings_production imported successfully"
    except Exception as e:
        return "fail", str(e)

def chk_django_setup():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "branch_system.settings_production")
    try:
        import django
        if not django.conf.settings.configured:
            django.setup()
        else:
            try:
                django.setup()
            except RuntimeError:
                pass  # already set up
        return "ok", "django.setup() completed"
    except Exception as e:
        return "fail", f"{e}\n        {traceback.format_exc()}"

check("python-dotenv / load .env", chk_dotenv)
check("Django importable", chk_django_import)
check("settings_production import", chk_settings_import)
check("django.setup()", chk_django_setup)

# ── 3. DATABASE ───────────────────────────────────────────────────────────────
print(f"\n{HEAD}[3] Database{END}")

def _setup():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "branch_system.settings_production")
    import django
    try:
        django.setup()
    except RuntimeError:
        pass

def chk_db_settings():
    _setup()
    from django.conf import settings
    db = settings.DATABASES["default"]
    return "ok", (f"ENGINE={db['ENGINE']}  NAME={db['NAME']}  "
                  f"USER={db['USER']}  HOST={db['HOST']}  PORT={db['PORT']}")

def chk_db_connect():
    _setup()
    from django.db import connection
    try:
        with connection.cursor() as c:
            c.execute("SELECT 1")
            c.fetchone()
        return "ok", "Connected to database"
    except Exception as e:
        return "fail", str(e)

def chk_migrations():
    _setup()
    from django.db import connection
    try:
        with connection.cursor() as c:
            c.execute("SELECT app, name FROM django_migrations ORDER BY app, id DESC")
            rows = c.fetchall()
        by_app = {}
        for app, name in rows:
            by_app.setdefault(app, []).append(name)
        summary = ", ".join(f"{a}({len(v)})" for a, v in sorted(by_app.items()))
        return "ok", f"django_migrations table exists. Apps: {summary}"
    except Exception as e:
        return "fail", f"Cannot read django_migrations: {e}"

def chk_unapplied_migrations():
    _setup()
    try:
        import subprocess
        result = subprocess.run(
            [sys.executable, "manage.py", "showmigrations", "--plan"],
            capture_output=True, text=True, timeout=60
        )
        unapplied = [l for l in result.stdout.splitlines() if l.strip().startswith("[ ]")]
        if unapplied:
            return "warn", f"{len(unapplied)} unapplied migrations:\n        " + "\n        ".join(unapplied[:20])
        if result.returncode != 0:
            return "fail", f"showmigrations failed: {result.stderr[:500]}"
        return "ok", "All migrations applied"
    except Exception as e:
        return "warn", f"Could not check migrations: {e}"

def chk_critical_tables():
    _setup()
    from django.db import connection
    required = [
        "users", "loans", "django_session", "django_migrations",
        "auth_permission", "django_content_type",
        "users_customuser_groups", "users_customuser_user_permissions",
    ]
    with connection.cursor() as c:
        c.execute("""
            SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
            WHERE TABLE_SCHEMA = DATABASE()
        """)
        existing = {r[0].lower() for r in c.fetchall()}
    missing = [t for t in required if t.lower() not in existing]
    if missing:
        return "fail", f"Missing tables: {missing}"
    return "ok", f"All critical tables present ({len(existing)} total tables)"

check("DB settings (from .env)", chk_db_settings)
check("DB connection", chk_db_connect)
check("django_migrations table", chk_migrations)
check("Unapplied migrations", chk_unapplied_migrations)
check("Critical tables exist", chk_critical_tables)

# ── 4. INSTALLED APPS & IMPORTS ───────────────────────────────────────────────
print(f"\n{HEAD}[4] App imports{END}")

def chk_app_import(app):
    def _chk():
        try:
            __import__(app)
            return "ok", f"{app} importable"
        except Exception as e:
            return "fail", f"{app}: {e}"
    return _chk

for app in ["users", "loans", "reports", "utils", "payments", "expenses", "grazuri"]:
    check(f"Import app: {app}", chk_app_import(app))

def chk_models():
    _setup()
    errors = []
    apps_to_check = ["users", "loans", "reports", "utils", "payments", "expenses"]
    for app in apps_to_check:
        try:
            mod = __import__(f"{app}.models", fromlist=["models"])
        except Exception as e:
            errors.append(f"{app}.models: {e}")
    if errors:
        return "fail", "\n        ".join(errors)
    return "ok", "All app models importable"

check("All app models importable", chk_models)

# ── 5. URL ROUTING ────────────────────────────────────────────────────────────
print(f"\n{HEAD}[5] URL routing{END}")

def chk_urls():
    _setup()
    try:
        from django.urls import reverse, NoReverseMatch
        from django.test import RequestFactory
        from django.conf import settings
        import importlib
        urls = importlib.import_module(settings.ROOT_URLCONF)
        return "ok", f"ROOT_URLCONF={settings.ROOT_URLCONF} loaded"
    except Exception as e:
        return "fail", str(e)

def chk_url_patterns():
    _setup()
    try:
        from django.urls import get_resolver
        resolver = get_resolver()
        count = len(resolver.url_patterns)
        return "ok", f"{count} top-level URL patterns registered"
    except Exception as e:
        return "fail", str(e)

check("URL conf importable", chk_urls)
check("URL patterns load", chk_url_patterns)

# ── 6. MIDDLEWARE ─────────────────────────────────────────────────────────────
print(f"\n{HEAD}[6] Middleware{END}")

def chk_middleware():
    _setup()
    from django.conf import settings
    errors = []
    for mw in settings.MIDDLEWARE:
        parts = mw.rsplit(".", 1)
        if len(parts) == 2:
            try:
                mod = __import__(parts[0], fromlist=[parts[1]])
                getattr(mod, parts[1])
            except Exception as e:
                errors.append(f"{mw}: {e}")
    if errors:
        return "fail", "\n        ".join(errors)
    return "ok", f"All {len(settings.MIDDLEWARE)} middleware classes importable"

check("All middleware importable", chk_middleware)

# ── 7. TEMPLATES ──────────────────────────────────────────────────────────────
print(f"\n{HEAD}[7] Templates{END}")

def chk_template_dirs():
    _setup()
    from django.conf import settings
    missing = []
    for tconf in settings.TEMPLATES:
        for d in tconf.get("DIRS", []):
            if not os.path.exists(str(d)):
                missing.append(str(d))
    if missing:
        return "warn", f"Template dirs missing: {missing}"
    return "ok", "All template DIRS exist"

def chk_base_template():
    _setup()
    from django.template.loader import get_template
    for name in ["base.html", "base/base.html", "layouts/base.html"]:
        try:
            get_template(name)
            return "ok", f"Found base template: {name}"
        except Exception:
            pass
    return "warn", "Could not find a base.html template (may be named differently)"

check("Template directories exist", chk_template_dirs)
check("Base template findable", chk_base_template)

# ── 8. STATIC & MEDIA FILES ───────────────────────────────────────────────────
print(f"\n{HEAD}[8] Static & media files{END}")

def chk_static_root():
    _setup()
    from django.conf import settings
    sr = str(settings.STATIC_ROOT)
    if not os.path.exists(sr):
        return "warn", f"STATIC_ROOT={sr} does not exist — run collectstatic"
    files = sum(len(f) for _, _, f in os.walk(sr))
    if files == 0:
        return "warn", f"STATIC_ROOT={sr} exists but is empty — run collectstatic"
    return "ok", f"STATIC_ROOT={sr} ({files} files)"

def chk_media_root():
    _setup()
    from django.conf import settings
    mr = str(settings.MEDIA_ROOT)
    if not os.path.exists(mr):
        return "warn", f"MEDIA_ROOT={mr} does not exist"
    return "ok", f"MEDIA_ROOT={mr} exists"

def chk_whitenoise():
    try:
        import whitenoise
        return "ok", f"whitenoise {whitenoise.__version__} installed"
    except ImportError:
        return "warn", "whitenoise not installed — static files may not serve in production"

check("STATIC_ROOT populated", chk_static_root)
check("MEDIA_ROOT exists", chk_media_root)
check("whitenoise installed", chk_whitenoise)

# ── 9. CRITICAL PACKAGE IMPORTS ───────────────────────────────────────────────
print(f"\n{HEAD}[9] Critical packages{END}")

packages = [
    ("django",          "Django"),
    ("pymysql",         "PyMySQL"),
    ("PIL",             "Pillow"),
    ("reportlab",       "ReportLab"),
    ("openpyxl",        "OpenPyXL"),
    ("dotenv",          "python-dotenv"),
    ("whitenoise",      "WhiteNoise"),
    ("phonenumbers",    "phonenumbers"),
    ("cryptography",    "cryptography"),
    ("magic",           "python-magic"),
]

def chk_pkg(mod, name):
    def _chk():
        try:
            m = __import__(mod)
            ver = getattr(m, "__version__", "?")
            return "ok", f"{name} {ver}"
        except ImportError as e:
            return "fail", f"{name} not installed: {e}"
    return _chk

for mod, name in packages:
    check(f"Package: {name}", chk_pkg(mod, name))

# ── 10. WSGI APPLICATION ──────────────────────────────────────────────────────
print(f"\n{HEAD}[10] WSGI application{END}")

def chk_wsgi():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "branch_system.settings_production")
    try:
        from django.core.wsgi import get_wsgi_application
        app = get_wsgi_application()
        return "ok", "WSGI application created successfully"
    except Exception as e:
        return "fail", f"{e}\n        {traceback.format_exc()}"

check("WSGI application", chk_wsgi)

# ── 11. SIMULATE A REQUEST ────────────────────────────────────────────────────
print(f"\n{HEAD}[11] Simulate GET /{END}")

def chk_request():
    _setup()
    try:
        from django.test import Client
        c = Client(SERVER_NAME="uzuriapps.xyz")
        resp = c.get("/", follow=False)
        code = resp.status_code
        if code < 500:
            return "ok", f"GET / returned HTTP {code}"
        return "fail", f"GET / returned HTTP {code} — server error"
    except Exception as e:
        return "fail", f"Request simulation failed: {e}\n        {traceback.format_exc()}"

def chk_login_page():
    _setup()
    try:
        from django.test import Client
        c = Client(SERVER_NAME="uzuriapps.xyz")
        resp = c.get("/login/", follow=False)
        code = resp.status_code
        if code < 500:
            return "ok", f"GET /login/ returned HTTP {code}"
        return "fail", f"GET /login/ returned HTTP {code}"
    except Exception as e:
        return "fail", f"Login page check failed: {e}"

check("Simulate GET /", chk_request)
check("Simulate GET /login/", chk_login_page)

# ── 12. DJANGO SYSTEM CHECKS ──────────────────────────────────────────────────
print(f"\n{HEAD}[12] Django system checks{END}")

def chk_system_check():
    _setup()
    try:
        import subprocess
        result = subprocess.run(
            [sys.executable, "manage.py", "check", "--deploy"],
            capture_output=True, text=True, timeout=60,
            env={**os.environ, "DJANGO_SETTINGS_MODULE": "branch_system.settings_production"}
        )
        output = result.stdout + result.stderr
        errors   = [l for l in output.splitlines() if "ERROR" in l]
        warnings = [l for l in output.splitlines() if "WARNING" in l]
        if errors:
            return "fail", "\n        ".join(errors[:10])
        if warnings:
            return "warn", f"{len(warnings)} warnings:\n        " + "\n        ".join(warnings[:5])
        return "ok", "No errors from manage.py check --deploy"
    except Exception as e:
        return "warn", f"Could not run system check: {e}"

check("manage.py check --deploy", chk_system_check)

# ── 13. LOG FILE TAIL ─────────────────────────────────────────────────────────
print(f"\n{HEAD}[13] Recent error logs{END}")

def chk_django_log():
    log_paths = [
        os.path.join(os.getcwd(), "logs", "django.log"),
        os.path.join(os.getcwd(), "django.log"),
        "/var/log/django.log",
    ]
    for path in log_paths:
        if os.path.exists(path):
            with open(path, encoding="utf-8", errors="replace") as f:
                lines = f.readlines()
            errors = [l.strip() for l in lines if "ERROR" in l or "CRITICAL" in l]
            recent = errors[-10:] if errors else []
            if recent:
                return "warn", f"Last {len(recent)} errors from {path}:\n        " + "\n        ".join(recent)
            return "ok", f"Log at {path} — no recent errors ({len(lines)} lines total)"
    return "warn", "No django.log found (checked logs/django.log)"

check("Django error log", chk_django_log)

# ── SUMMARY ───────────────────────────────────────────────────────────────────
print(f"\n{HEAD}{'='*65}{END}")
print(f"{HEAD}  SUMMARY{END}")
print(f"{HEAD}{'='*65}{END}")

fails  = [(l, d) for s, l, d in results if s == "fail"]
warns  = [(l, d) for s, l, d in results if s == "warn"]
oks    = [(l, d) for s, l, d in results if s == "ok"]

print(f"  {OK}{len(oks)} passed")
print(f"  {WARN}{len(warns)} warnings")
print(f"  {FAIL}{len(fails)} failures")

if fails:
    print(f"\n{HEAD}  FAILURES (fix these first):{END}")
    for label, detail in fails:
        print(f"  {FAIL}{label}")
        if detail:
            for line in detail.splitlines():
                print(f"        {line}")

if warns:
    print(f"\n{HEAD}  WARNINGS:{END}")
    for label, detail in warns:
        print(f"  {WARN}{label}")
        if detail:
            for line in detail.splitlines():
                print(f"        {line}")

print(f"\n{HEAD}  Diagnostic complete.{END}\n")
