#!/usr/bin/env python3
"""
Smoke test Central Admin.

Menjalankan pemeriksaan end-to-end terhadap instance yang sedang berjalan:
login, membuka seluruh halaman utama, dan mengirim form module (Brand, Content,
Availability, WhatsApp, SEO) secara otomatis.

Pemakaian:
    python3 tests/smoke_test.py --base=http://127.0.0.1:8000 \
        --email=admin@central-admin.test --password=secret

Exit code 0 = semua lolos, 1 = ada kegagalan.
"""
from __future__ import annotations

import argparse
import json
import re
import sys

import requests
from bs4 import BeautifulSoup

PAGES = [
    "/dashboard", "/websites", "/websites/create", "/modules", "/activity",
    "/notifications", "/users", "/users/create", "/permissions",
    "/settings", "/settings/profile", "/settings/security", "/settings/api",
    "/settings/notifications", "/settings/appearance", "/settings/system",
    "/search?q=live", "/notifications/feed", "/health", "/robots.txt",
]

APP_PAGES = [
    "", "/settings", "/modules", "/modules/add", "/modules/add?tab=install",
    "/modules/brand", "/modules/content",
    "/brand", "/content", "/content/services", "/content/packages",
    "/availability", "/whatsapp", "/seo",
]

FORMS = [
    ("/apps/{app}/brand", "/brand", "brand.update"),
    ("/apps/{app}/content", "/content/hero", "content.hero.update"),
    ("/apps/{app}/content/services", "/content/services/save", "content.services.save"),
    ("/apps/{app}/content/packages", "/content/packages/save", "content.packages.save"),
    ("/apps/{app}/availability", "/availability/slots/save", "availability.slots.save"),
    ("/apps/{app}/whatsapp", "/whatsapp", "whatsapp.update"),
    ("/apps/{app}/seo", "/seo", "seo.update"),
]


def token_of(html: str) -> str | None:
    match = re.search(r'name="_token" value="([^"]+)"', html)
    return match.group(1) if match else None


def value_for(name: str, field_type: str, existing: str, field) -> str:
    """Nilai wajar berdasarkan nama/tipe field agar validasi aplikasi lolos."""
    if existing:
        return existing
    lowered = name.lower()
    if lowered.endswith("_url") or "url" in lowered or field_type == "url":
        return "https://example.com/smoke.png"
    if "icon" in lowered:
        return "star"
    if "email" in lowered or field_type == "email":
        return "smoke@central-admin.test"
    if "wa_number" in lowered or "whatsapp" in lowered or "phone" in lowered or "telp" in lowered:
        return "081234567890"
    if field_type == "date":
        return "2026-10-01"
    if field_type == "time":
        return "09:00"
    if field_type == "number":
        return field.get("min") or "1"
    if any(key in lowered for key in ("description", "body", "message", "template", "subheadline", "about")):
        return "Deskripsi uji otomatis untuk Central Admin smoke test."
    if "price" in lowered or "amount" in lowered:
        return "1500000"
    if any(key in lowered for key in ("title", "headline", "label", "tagline", "keywords")):
        return "Smoke Test Central Admin"
    if "slug" in lowered or "code" in lowered or "id" in lowered:
        return "smoke-test"
    return "Smoke Test"


def fill_form(form) -> dict:
    """Isi seluruh field form dengan nilai yang wajar untuk pengujian."""
    data: dict[str, str] = {}
    for field in form.find_all(["input", "textarea", "select"]):
        name = field.get("name")
        if not name or name == "_token":
            continue
        if field.name == "select":
            options = [o for o in field.find_all("option") if not o.has_attr("disabled")]
            value = ""
            for option in options:
                if option.has_attr("selected"):
                    value = option.get("value", "")
                    break
            if value == "" and options:
                value = options[0].get("value", "")
            data[name] = value if value != "" else value_for(name, "text", "", field)
            continue
        field_type = field.get("type", "text")
        if field_type == "checkbox":
            if field.has_attr("checked"):
                data[name] = field.get("value", "1")
            continue
        if field_type == "file":
            continue
        if field_type == "hidden":
            data[name] = field.get("value", "")
            continue
        existing = field.get("value") or (field.text.strip() if field.name == "textarea" else "")
        if existing and not any(placeholder in existing for placeholder in ("Contoh", "contoh", "placeholder")):
            data[name] = existing
            continue
        data[name] = value_for(name, field_type, "", field)
    return data


class Smoke:
    def __init__(self, base: str, email: str, password: str, app_slug: str) -> None:
        self.base = base.rstrip("/")
        self.email = email
        self.password = password
        self.app = app_slug
        self.session = requests.Session()
        self.session.headers.update({"User-Agent": "CentralAdminSmokeTest/1.0"})
        self.failures: list[str] = []

    # ---------------------------------------------------------------- helpers
    def path_of(self, url: str, fallback: str = "/") -> str:
        """Ubah URL absolut/relatif menjadi path relatif terhadap base."""
        if not url:
            return fallback
        if url.startswith(self.base):
            return url[len(self.base):] or "/"
        return url

    def get(self, path: str, **kwargs):
        return self.session.get(self.base + self.path_of(path), timeout=30, **kwargs)

    def fail(self, message: str) -> None:
        self.failures.append(message)
        print(f"  ✗ {message}")

    def ok(self, message: str) -> None:
        print(f"  ✓ {message}")

    # ------------------------------------------------------------------ steps
    def login(self) -> None:
        page = self.get("/login")
        response = self.session.post(
            self.base + "/login",
            data={"_token": token_of(page.text), "email": self.email, "password": self.password},
            headers={"Referer": self.base + "/login"},
            allow_redirects=False,
            timeout=30,
        )
        if response.status_code != 302 or "/dashboard" not in response.headers.get("Location", ""):
            self.fail(f"login gagal (HTTP {response.status_code})")
            raise SystemExit(1)
        self.ok("login berhasil")

    def check_pages(self, paths: list[str]) -> None:
        for path in paths:
            response = self.get(path)
            if response.status_code >= 400:
                self.fail(f"GET {path} -> {response.status_code}")
            else:
                self.ok(f"GET {path} -> {response.status_code}")

    def submit_form(self, page_path: str, action_fragment: str, label: str) -> None:
        page = self.get(page_path)
        soup = BeautifulSoup(page.text, "html.parser")
        forms = [f for f in soup.find_all("form") if action_fragment in (f.get("action") or "")]
        if not forms:
            self.fail(f"{label}: form tidak ditemukan pada {page_path}")
            return

        form = forms[0]
        action = self.path_of(form.get("action") or "", page_path)
        data = fill_form(form)
        token = token_of(page.text)
        if token:
            data["_token"] = token

        response = self.session.post(
            self.base + action, data=data,
            headers={"Referer": self.base + page_path},
            allow_redirects=False, timeout=30,
        )
        if response.status_code not in (200, 302):
            self.fail(f"{label}: HTTP {response.status_code}")
            return

        follow = self.get(self.path_of(response.headers.get("Location", ""), page_path))
        errors = re.findall(r'toast is-error"><div class="toast-body"><strong>[^<]*</strong><p>([^<]*)</p>', follow.text)
        if errors:
            self.fail(f"{label}: {errors[0]}")
            return
        self.ok(f"{label}: form terkirim")

    def run(self) -> int:
        print("· login")
        self.login()

        print("· halaman Core")
        self.check_pages(PAGES)

        print(f"· halaman App ({self.app})")
        self.check_pages([f"/apps/{self.app}{suffix}" for suffix in APP_PAGES])

        print("· form module App")
        for page_path, action_fragment, label in FORMS:
            self.submit_form(page_path.format(app=self.app), action_fragment, label)

        print()
        if self.failures:
            print(f"HASIL: {len(self.failures)} kegagalan")
            for item in self.failures:
                print(" -", item)
            return 1
        print("HASIL: semua pemeriksaan lolos")
        return 0


def main() -> int:
    parser = argparse.ArgumentParser(description="Smoke test Central Admin")
    parser.add_argument("--base", default="http://127.0.0.1:8000")
    parser.add_argument("--email", default="admin@central-admin.test")
    parser.add_argument("--password", default="")
    parser.add_argument("--app", default="live-production", help="slug App untuk workspace")
    parser.add_argument("--json", action="store_true", help="cetak ringkasan JSON")
    args = parser.parse_args()

    if args.password == "":
        print("Password wajib diisi: --password=...", file=sys.stderr)
        return 2

    smoke = Smoke(args.base, args.email, args.password, args.app)
    exit_code = smoke.run()
    if args.json:
        print(json.dumps({"failures": smoke.failures}, indent=2))
    return exit_code


if __name__ == "__main__":
    raise SystemExit(main())
