#!/usr/bin/env python3
"""Comprehensive System Diagnostics & Health Checker for DSCons ERP & dinhsonconstruction.com

Checks:
1. System network & listening ports (Local & Tailscale mesh)
2. Database (PostgreSQL) tables, schema, data integrity
3. AI Inference engines (LM Studio, OpenRouter, Qdrant)
4. Cloudflare Tunnel process & configuration
5. Website & API Endpoints (both Local Origin and Public HTTPS dinhsonconstruction.com)
6. Static assets integrity (CSS, JS, Logos)
7. Log generation & structured reporting (JSON + Markdown)
"""

import asyncio
import datetime
import json
import os
import shutil
import socket
import subprocess
import sys
import time
from pathlib import Path

import httpx

try:
    import psycopg
except ImportError:
    try:
        import psycopg2 as psycopg
    except ImportError:
        psycopg = None
from dotenv import load_dotenv

# Ensure base directory is in sys.path
BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(BASE_DIR))
load_dotenv(BASE_DIR / ".env")

LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)

REPORT_FILE_JSON = LOG_DIR / "system_diagnostics_report.json"
REPORT_FILE_LOG = LOG_DIR / "system_diagnostics.log"


def log(msg: str, level: str = "INFO"):
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    formatted = f"[{timestamp}] [{level}] {msg}"
    print(formatted)
    with open(REPORT_FILE_LOG, "a", encoding="utf-8") as f:
        f.write(formatted + "\n")


def check_tcp_port(host: str, port: int, timeout: float = 2.0) -> dict:
    start = time.time()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    try:
        res = sock.connect_ex((host, port))
        latency = round((time.time() - start) * 1000, 2)
        if res == 0:
            return {"status": "OPEN", "latency_ms": latency}
        else:
            return {"status": "CLOSED", "error_code": res, "latency_ms": latency}
    except Exception as e:
        return {
            "status": "ERROR",
            "error": str(e),
            "latency_ms": round((time.time() - start) * 1000, 2),
        }
    finally:
        sock.close()


def check_tailscale() -> dict:
    log("Scanning Tailscale network status...")
    tailscale_bin = shutil.which("tailscale") or "tailscale"
    try:
        proc = subprocess.run(
            [tailscale_bin, "status", "--json"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if proc.returncode == 0:
            data = json.loads(proc.stdout)
            self_node = data.get("Self", {})
            peers = data.get("Peer", {})
            peer_summary = []
            for k, p in peers.items():
                peer_summary.append(
                    {
                        "host_name": p.get("HostName"),
                        "ip": p.get("TailscaleIPs", [None])[0],
                        "os": p.get("OS"),
                        "online": p.get("Online"),
                        "active": p.get("Active"),
                        "last_seen": p.get("LastSeen"),
                    }
                )
            return {
                "installed": True,
                "self": {
                    "host_name": self_node.get("HostName"),
                    "ip": self_node.get("TailscaleIPs", [None])[0],
                    "os": self_node.get("OS"),
                    "online": self_node.get("Online"),
                },
                "peers": peer_summary,
            }
        else:
            return {"installed": True, "error": proc.stderr.strip()}
    except Exception as e:
        return {"installed": False, "error": str(e)}


def check_postgres() -> dict:
    log("Checking PostgreSQL database connection and tables...")
    db_url = os.getenv("DATABASE_URL")
    if not db_url:
        return {"status": "ERROR", "error": "DATABASE_URL not configured in .env"}

    start = time.time()
    try:
        conn = psycopg.connect(db_url, connect_timeout=3)
        cur = conn.cursor()
        cur.execute("SELECT current_database(), current_user, version();")
        db_name, db_user, db_version = cur.fetchone()

        cur.execute("""
            SELECT table_name 
            FROM information_schema.tables 
            WHERE table_schema = 'public'
            ORDER BY table_name;
        """)
        tables = [row[0] for row in cur.fetchall()]

        # Query counts of critical business tables
        table_counts = {}
        for t in [
            "projects",
            "partners",
            "site_workers",
            "project_items",
            "invoices",
            "equipment",
            "equipment_logs",
            "users",
            "documents",
            "financial_transactions",
        ]:
            if t in tables:
                try:
                    cur.execute(f"SELECT count(*) FROM public.{t};")
                    table_counts[t] = cur.fetchone()[0]
                except Exception as ex:
                    table_counts[t] = f"Error: {ex}"

        cur.close()
        conn.close()
        return {
            "status": "HEALTHY",
            "database": db_name,
            "user": db_user,
            "version": db_version,
            "latency_ms": round((time.time() - start) * 1000, 2),
            "total_tables": len(tables),
            "tables": tables,
            "sample_counts": table_counts,
        }
    except Exception as e:
        return {
            "status": "UNHEALTHY",
            "error": str(e),
            "latency_ms": round((time.time() - start) * 1000, 2),
        }


async def check_lmstudio() -> dict:
    log("Checking LM Studio Local AI inference engine...")
    base_url = os.getenv("LMSTUDIO_BASE_URL", "http://127.0.0.1:1234/v1")
    try:
        async with httpx.AsyncClient(timeout=3.0) as client:
            res = await client.get(f"{base_url}/models")
            if res.status_code == 200:
                data = res.json()
                models = [m.get("id") for m in data.get("data", [])]
                return {
                    "status": "ONLINE",
                    "base_url": base_url,
                    "loaded_models": models,
                }
            return {
                "status": "DEGRADED",
                "status_code": res.status_code,
                "body": res.text,
            }
    except Exception as e:
        return {"status": "OFFLINE", "error": str(e), "base_url": base_url}


async def check_qdrant() -> dict:
    log("Checking Qdrant vector database...")
    qdrant_url = os.getenv("QDRANT_URL", "http://127.0.0.1:6333")
    try:
        async with httpx.AsyncClient(timeout=3.0) as client:
            res = await client.get(f"{qdrant_url}/collections")
            if res.status_code == 200:
                data = res.json()
                collections = [
                    c.get("name") for c in data.get("result", {}).get("collections", [])
                ]
                return {"status": "ONLINE", "collections": collections}
            return {"status": "DEGRADED", "status_code": res.status_code}
    except Exception as e:
        return {
            "status": "OFFLINE",
            "error": str(e),
            "note": "Vector search will fallback gracefully",
        }


def check_cloudflared_daemon() -> dict:
    log("Checking Cloudflare Tunnel daemon & config...")
    user_profile = os.environ.get("USERPROFILE") or os.path.expanduser("~")
    config_path = Path(user_profile) / ".cloudflared" / "config.yml"

    config_exists = config_path.exists()
    config_content = None
    if config_exists:
        try:
            with open(config_path, "r", encoding="utf-8") as f:
                config_content = f.read()
        except Exception:
            pass

    # Check process list for cloudflared
    try:
        if os.name == "nt":
            res = subprocess.run(
                ["tasklist", "/FI", "IMAGENAME eq cloudflared.exe"],
                capture_output=True,
                text=True,
            )
            is_running = "cloudflared.exe" in res.stdout
        else:
            res = subprocess.run(
                ["pgrep", "-f", "cloudflared"], capture_output=True, text=True
            )
            is_running = res.returncode == 0
    except Exception:
        is_running = False

    return {
        "config_path": str(config_path),
        "config_exists": config_exists,
        "config_preview": config_content[:200] if config_content else None,
        "process_running": is_running,
    }


async def test_endpoint(
    client: httpx.AsyncClient, base_url: str, path: str, expected_status=(200, 307, 308)
) -> dict:
    url = f"{base_url}{path}"
    start = time.time()
    try:
        res = await client.get(url, follow_redirects=False)
        latency = round((time.time() - start) * 1000, 2)
        passed = res.status_code in expected_status or (200 <= res.status_code < 400)
        return {
            "path": path,
            "url": url,
            "status_code": res.status_code,
            "passed": passed,
            "latency_ms": latency,
            "content_type": res.headers.get("content-type", ""),
            "content_length": len(res.content),
        }
    except Exception as e:
        return {
            "path": path,
            "url": url,
            "status_code": None,
            "passed": False,
            "error": str(e),
            "latency_ms": round((time.time() - start) * 1000, 2),
        }


async def test_website_and_api(base_url: str) -> dict:
    log(f"Testing endpoints on target: {base_url} ...")
    endpoints = [
        # Public Pages & Static Assets
        "/",
        "/health",
        "/health/readiness",
        "/login",
        "/static/css/erp_enterprise.css",
        "/static/js/erp_mobile_dock.js",
        # ERP Modules & Dashboard UI Pages
        "/dashboard",
        "/dashboard/projects",
        "/dashboard/employees",
        "/dashboard/equipment",
        "/dashboard/readiness",
        "/dashboard/documents",
        "/dashboard/invoices",
        "/dashboard/partners",
        "/dashboard/war-room",
        "/dashboard/agent-models",
        "/dashboard/users",
        "/dashboard/site-diary-mobile",
        # REST API Endpoints (Public / Unauthenticated / Stats)
        "/api/v1/partners",
        "/v1/war-room/summary",
        "/v1/war-room/pillars",
        "/v1/war-room/cashflow",
    ]

    results = []
    async with httpx.AsyncClient(timeout=10.0, verify=False) as client:
        for ep in endpoints:
            res = await test_endpoint(client, base_url, ep)
            results.append(res)
            log(f" -> [{res.get('status_code')}] {ep} ({res.get('latency_ms')}ms)")

    passed_count = sum(1 for r in results if r["passed"])
    return {
        "base_url": base_url,
        "total_tested": len(results),
        "passed": passed_count,
        "failed": len(results) - passed_count,
        "endpoints": results,
    }


async def main():
    log(
        "================================================================================"
    )
    log(
        "           STARTING COMPREHENSIVE DSCONS SYSTEM & WEBSITE DIAGNOSTICS           "
    )
    log(
        "================================================================================"
    )

    # 1. Ports scan
    ports_to_check = [
        ("FastAPI Backend", "127.0.0.1", 8000),
        ("PostgreSQL Database", "127.0.0.1", 5432),
        ("LM Studio Inference", "127.0.0.1", 1234),
        ("Qdrant Vector DB", "127.0.0.1", 6333),
        ("SSH Server", "127.0.0.1", 22),
    ]
    port_results = {}
    for name, host, p in ports_to_check:
        port_results[f"{name} ({host}:{p})"] = check_tcp_port(host, p)

    # 2. Tailscale Mesh
    tailscale_results = check_tailscale()

    # 3. PostgreSQL Database
    postgres_results = check_postgres()

    # 4. AI Inference & Vector DB
    lmstudio_results = await check_lmstudio()
    qdrant_results = await check_qdrant()

    # 5. Cloudflare Tunnel
    cloudflared_results = check_cloudflared_daemon()

    # 6. HTTP & API Tests on Local
    local_http_results = await test_website_and_api("http://127.0.0.1:8000")

    # 7. HTTP & API Tests on Public Domain dinhsonconstruction.com
    public_http_results = await test_website_and_api("https://dinhsonconstruction.com")

    # Aggregate Health Summary
    overall_healthy = (
        postgres_results.get("status") == "HEALTHY"
        and local_http_results.get("failed") == 0
        and public_http_results.get("failed") == 0
        and cloudflared_results.get("process_running") is True
    )

    report = {
        "timestamp": datetime.datetime.now().isoformat(),
        "overall_status": "HEALTHY" if overall_healthy else "NEEDS_ATTENTION",
        "ports": port_results,
        "tailscale": tailscale_results,
        "database": postgres_results,
        "lmstudio": lmstudio_results,
        "qdrant": qdrant_results,
        "cloudflared": cloudflared_results,
        "local_origin_http": local_http_results,
        "public_domain_http": public_http_results,
    }

    # Save to JSON
    with open(REPORT_FILE_JSON, "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)

    # Save Markdown Audit Report
    audit_md_path = BASE_DIR / "docs" / "audits" / "system_health_audit_2026_08_24.md"
    audit_md_path.parent.mkdir(parents=True, exist_ok=True)

    md_content = f"""# BÁO CÁO KIỂM TRA HỆ THỐNG & WEBSITE DINHSONCONSTRUCTION.COM
*Thời gian kiểm tra: {datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")} (UTC+7)*
*Trạng thái tổng thể: **{report["overall_status"]}***

---

## 1. KẾT QUẢ QUÉT CỔNG DỊCH VỤ (SERVICE PORTS)
| Dịch Vụ | Địa Chỉ & Cổng | Trạng Thái | Độ Trễ (Latency) |
|---|---|---|---|
"""
    for name, data in port_results.items():
        status_badge = (
            "🟢 ONLINE" if data.get("status") == "OPEN" else f"🔴 {data.get('status')}"
        )
        lat = f"{data.get('latency_ms', 0)} ms"
        md_content += f"| {name} | {status_badge} | {lat} |\n"

    md_content += f"""
---

## 2. CƠ SỞ DỮ LIỆU POSTGRESQL (GROUND TRUTH)
- **Trạng thái:** {"🟢 HEALTHY" if postgres_results.get("status") == "HEALTHY" else "🔴 UNHEALTHY"}
- **Database:** `{postgres_results.get("database")}`
- **User:** `{postgres_results.get("user")}`
- **Phiên bản:** {postgres_results.get("version")}
- **Tổng số bảng (Public Schema):** **{postgres_results.get("total_tables", 0)} bảng**
- **Mẫu dữ liệu thực tế:**
"""
    for table_name, count in postgres_results.get("sample_counts", {}).items():
        md_content += f"  - `public.{table_name}`: **{count}** bản ghi\n"

    md_content += f"""
---

## 3. MẠNG TAILSCALE & MÁY CHỦ LIÊN KẾT
- **Node hiện tại (Self):** `{tailscale_results.get("self", {}).get("host_name")}` ({tailscale_results.get("self", {}).get("ip")}) - OS: {tailscale_results.get("self", {}).get("os")}
- **Thiết bị Peer trong Mesh:**
"""
    for peer in tailscale_results.get("peers", []):
        peer_status = "🟢 ONLINE" if peer.get("online") else "⚪ OFFLINE"
        md_content += f"  - `{peer.get('host_name')}` ({peer.get('ip')}) | OS: {peer.get('os')} | Trạng thái: {peer_status} | Hoạt động gần nhất: {peer.get('last_seen')}\n"

    md_content += f"""
---

## 4. HẠ TẦNG CLOUDFLARE TUNNEL (PUBLIC EDGE)
- **Tiến trình Cloudflared:** {"🟢 ĐANG CHẠY" if cloudflared_results.get("process_running") else "🔴 DỪNG"}
- **File cấu hình:** `{cloudflared_results.get("config_path")}`
- **Domain trỏ về:** `dinhsonconstruction.com` -> `http://127.0.0.1:8000`

---

## 5. KIỂM TRA ĐIỂM TRUY CẬP WEBSITE & API (ENDPOINT AUDIT)

### Bảng đối chiếu Local Origin vs Public Domain:
| Đường dẫn (Endpoint) | Loại | Local (127.0.0.1:8000) | Public (dinhsonconstruction.com) | Trạng Thái |
|---|---|---|---|---|
"""
    local_eps = {ep["path"]: ep for ep in local_http_results.get("endpoints", [])}
    public_eps = {ep["path"]: ep for ep in public_http_results.get("endpoints", [])}

    for path in local_eps:
        loc = local_eps.get(path, {})
        pub = public_eps.get(path, {})
        loc_code = loc.get("status_code", "ERR")
        loc_lat = f"{loc.get('latency_ms', 0)}ms"
        pub_code = pub.get("status_code", "ERR")
        pub_lat = f"{pub.get('latency_ms', 0)}ms"

        ok = loc.get("passed", False) and pub.get("passed", False)
        badge = "🟢 200 OK" if ok else f"⚠️ {loc_code}/{pub_code}"

        md_content += f"| `{path}` | UI/API | {loc_code} ({loc_lat}) | {pub_code} ({pub_lat}) | {badge} |\n"

    md_content += f"""
---

## 6. ĐỘNG CƠ TRÍ TUỆ NHÂN TẠO & VECTOR STORE
- **LM Studio Local Engine:** {lmstudio_results.get("status")} ({lmstudio_results.get("base_url")}) - Mô hình đã nạp: `{lmstudio_results.get("loaded_models")}`
- **Qdrant Vector DB:** {qdrant_results.get("status")} ({qdrant_results.get("note", "")})

---
*Báo cáo được khởi tạo tự động bởi hệ thống kiểm soát chất lượng & chẩn đoán hạ tầng DSCons.*
"""

    with open(audit_md_path, "w", encoding="utf-8") as f:
        f.write(md_content)

    log(f" Markdown audit report generated at: {audit_md_path}")
    log(
        "================================================================================"
    )
    log(f" DIAGNOSTICS COMPLETED. Overall Status: {report['overall_status']}")
    log(f" Full JSON report saved to: {REPORT_FILE_JSON}")
    log(f" Full execution log saved to: {REPORT_FILE_LOG}")
    log(
        "================================================================================"
    )


if __name__ == "__main__":
    asyncio.run(main())
