#!/usr/bin/env python3
"""
Smart migration runner for production.
Tries each unapplied migration; if it fails with a duplicate column/table
error (meaning the DB already has it from the old schema), fakes it instead.

Run on server: python smart_migrate.py
"""
import os
import sys
import subprocess
from datetime import datetime

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branch_system.settings_production')

try:
    from dotenv import load_dotenv
    load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env'), override=True)
except ImportError:
    pass

import django
django.setup()
from django.db import connection

py = sys.executable

# Errors that mean "already exists in DB — just fake it"
ALREADY_EXISTS_ERRORS = (
    "already exists",       # 1050 table already exists
    "duplicate column",     # 1060 duplicate column name
    "duplicate key name",   # 1061 duplicate index
    "multiple primary key", # 1068
)

def run_cmd(cmd):
    result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
    return result.returncode, result.stdout + result.stderr

def get_unapplied():
    """Return list of (app, migration_name) that are not yet applied."""
    code, out = run_cmd(f"{py} manage.py showmigrations --plan")
    unapplied = []
    for line in out.splitlines():
        line = line.strip()
        if line.startswith("[ ]"):
            # format: "[ ] app.migration_name"
            parts = line[3:].strip().split(".")
            if len(parts) == 2:
                unapplied.append((parts[0].strip(), parts[1].strip()))
    return unapplied

def fake_migration(app, name):
    code, out = run_cmd(f"{py} manage.py migrate {app} {name} --fake")
    return code == 0, out

def run_migration(app, name):
    code, out = run_cmd(f"{py} manage.py migrate {app} {name}")
    return code == 0, out

print(f"\n{'='*60}")
print(f"  Smart Migration Runner  {datetime.now():%Y-%m-%d %H:%M:%S}")
print(f"{'='*60}")

# ── Phase 1: Run all unapplied migrations, faking conflicts ──
print("\n[1] Processing unapplied migrations...\n")

unapplied = get_unapplied()
print(f"  Found {len(unapplied)} unapplied migrations\n")

faked = []
applied = []
failed = []

for app, name in unapplied:
    print(f"  Applying {app}.{name}...")
    ok, out = run_migration(app, name)
    if ok:
        print(f"    OK")
        applied.append(f"{app}.{name}")
    else:
        # Check if it's a "already exists" type error
        out_lower = out.lower()
        if any(err in out_lower for err in ALREADY_EXISTS_ERRORS):
            print(f"    CONFLICT (already exists) — faking...")
            fake_ok, fake_out = fake_migration(app, name)
            if fake_ok:
                print(f"    FAKED OK")
                faked.append(f"{app}.{name}")
            else:
                print(f"    FAKE ALSO FAILED:\n{fake_out[:300]}")
                failed.append(f"{app}.{name}")
        else:
            print(f"    FAILED:\n    {out[:400]}")
            failed.append(f"{app}.{name}")

# ── Phase 2: Final migrate to catch anything remaining ────────
print(f"\n[2] Final migrate pass...")
code, out = run_cmd(f"{py} manage.py migrate --run-syncdb")
print(out[:1000] if out else "  (no output)")

# ── Phase 3: Create cache table ───────────────────────────────
print(f"\n[3] Cache table...")
run_cmd(f"{py} manage.py createcachetable")

# ── Phase 4: Verify critical tables ──────────────────────────
print(f"\n[4] Verifying critical tables...")
critical = [
    'django_session', 'django_migrations', 'users', 'loans',
    'utils_systemsetting', 'utils_notification', 'auth_permission',
    'django_content_type', 'cache_table', 'role_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()}

all_ok = True
for table in critical:
    status = "OK     " if table.lower() in existing else "MISSING"
    if table.lower() not in existing:
        all_ok = False
    print(f"  {status}  {table}")

# ── Summary ───────────────────────────────────────────────────
print(f"\n{'='*60}")
print(f"  Applied normally : {len(applied)}")
print(f"  Faked (conflict) : {len(faked)}")
print(f"  Failed           : {len(failed)}")

if faked:
    print(f"\n  Faked migrations (DB already had these):")
    for m in faked:
        print(f"    {m}")

if failed:
    print(f"\n  Failed migrations (need attention):")
    for m in failed:
        print(f"    {m}")

print(f"\n{'='*60}")
if all_ok and not failed:
    print("  SUCCESS — restart your app in cPanel.")
elif not failed:
    print("  All migrations processed. Restart app in cPanel.")
else:
    print("  Some migrations failed — check output above.")
print(f"{'='*60}\n")
