"""
NRI Property Guard — Website Functional Test Suite (Selenium / Python)
============================================================================

This is the Selenium counterpart to tests/test_playwright_suite.py. It was
written to run in any normal environment with Chrome/Chromium and a matching
chromedriver (or Selenium 4's built-in Selenium Manager, which auto-resolves
one). Earlier phases of this build couldn't get a Selenium session started at
all inside the sandbox used to build this site (a chromedriver/Chromium
version mismatch with no network path to resolve it); as of Phase 10 it runs
for real there too, against a separately-installed Chrome build pointed at by
CHROME_BINARY/CHROMEDRIVER_PATH — see README.md in this folder for exactly
how, and for the real 19/19 (+1 intentionally skipped) results that run
produced. This file is real, complete, runnable code either way: point it at
your own environment (local machine or CI) and it will drive an actual
browser end to end.

Usage:
    pip install -r requirements.txt
    python3 -m http.server 8899          # from the site/ root, in another terminal
    python3 test_functionality.py         # or: pytest test_functionality.py -v

Environment variables:
    BASE_URL        Override the site under test (default http://localhost:8899)
    CHROME_BINARY   Path to a specific Chrome/Chromium executable to drive.
    CHROMEDRIVER_PATH
                     Path to a specific chromedriver executable, instead of
                     Selenium Manager's auto-resolution (which needs outbound
                     access to googlechromelabs.github.io).
    RUN_LIVE_LEAD_SUBMIT
                     Set to "1" to actually let the contact form POST to the
                     live Supabase website_leads table. Left unset (default),
                     the lead-form test only fills and validates the form and
                     does NOT submit it, so running this suite never writes
                     synthetic rows into production data.

Note on role-based dashboard routing (Admin / Property Manager / Property
Executive / Customer, each landing on its own dashboard after login): that
regression coverage lives only in the Playwright suite, via a fully mocked
Supabase client (see tests/test_playwright_suite.py, section 8b). It isn't
duplicated here because safely exercising four different account roles needs
either real staff credentials for each role (which this suite should not
require just to run) or the same kind of full network-level client stub the
Playwright suite already uses — Selenium has no equivalent to Playwright's
page.route() for swapping out a whole same-origin script file, so replicating
that stub here would mean a materially different technique, not a port of the
existing tests. If you want this covered end-to-end against a real Chrome
driver, the straightforward path is real test accounts for each of the five
roles, signing in as each and asserting on the landing URL.
"""

import json
import os
import time
import unittest

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException

BASE_URL = os.environ.get("BASE_URL", "http://localhost:8899")
RUN_LIVE_LEAD_SUBMIT = os.environ.get("RUN_LIVE_LEAD_SUBMIT") == "1"

MARKETING_PAGES = [
    "/index.html", "/services.html", "/how-it-works.html", "/pricing.html",
    "/about.html", "/faq.html", "/contact.html",
    "/privacy-policy.html", "/terms.html",
]


def make_driver(mobile=False):
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-gpu")
    if mobile:
        options.add_argument("--window-size=390,844")
    else:
        options.add_argument("--window-size=1280,900")

    # CHROME_BINARY lets you point this at a specific Chrome/Chromium install
    # (useful when Selenium Manager's own download is blocked by a network
    # allowlist, as it was in the sandbox this site was built in — see
    # README.md's "Actually running this suite" section for how a
    # chromedriver already on PATH there turned out to work fine against a
    # separately-installed Chrome one major version apart, once pointed at it
    # explicitly instead of relying on auto-resolution).
    chrome_binary = os.environ.get("CHROME_BINARY")
    if chrome_binary:
        options.binary_location = chrome_binary

    chromedriver_path = os.environ.get("CHROMEDRIVER_PATH")
    if chromedriver_path:
        from selenium.webdriver.chrome.service import Service
        return webdriver.Chrome(service=Service(executable_path=chromedriver_path), options=options)

    # Default: Selenium 4's Selenium Manager auto-resolves a matching
    # chromedriver (needs outbound internet access the first time). If your
    # environment already has one on PATH, this still works unchanged.
    return webdriver.Chrome(options=options)


def click_safely(driver, element):
    """Scroll an element to the viewport's center before clicking it.

    WebDriver's native click() is supposed to auto-scroll a target into view
    first, but that doesn't always happen reliably — most visibly when the
    chromedriver build in use isn't an exact version match for the Chrome
    build it's driving (see README.md's "Actually running this suite"
    section for why that pairing is sometimes unavoidable). An explicit
    scrollIntoView removes that as a source of flaky
    ElementClickInterceptedException/ElementNotInteractableException
    failures that have nothing to do with the site itself.

    behavior: 'instant' matters here, not just style — this site sets
    `html { scroll-behavior: smooth }` globally, so a default scrollIntoView
    call animates over several frames; click() was firing (and computing its
    click point) before that animation finished, landing on whatever was at
    the OLD position instead. Forcing an instant jump removes that race.
    """
    driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});", element)
    time.sleep(0.15)
    element.click()


class SiteLoadsCleanly(unittest.TestCase):
    """Every page returns 200-equivalent content, has exactly one <h1>, no JS errors."""

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def _get_console_errors(self):
        try:
            logs = self.driver.get_log("browser")
        except Exception:
            return []  # not all drivers/browsers expose this the same way
        return [l["message"] for l in logs if l["level"] == "SEVERE"
                and "ERR_TUNNEL_CONNECTION_FAILED" not in l["message"]
                and "fonts.googleapis.com" not in l["message"]]

    def test_all_pages_load_with_single_h1(self):
        for path in MARKETING_PAGES + ["/portal/index.html"]:
            with self.subTest(path=path):
                self.driver.get(BASE_URL + path)
                self.assertIn("NRI Property Guard", self.driver.title)
                h1s = self.driver.find_elements(By.TAG_NAME, "h1")
                self.assertEqual(len(h1s), 1, f"{path} should have exactly one <h1>")
                errors = self._get_console_errors()
                self.assertEqual(errors, [], f"Unexpected console errors on {path}: {errors}")


class SeoMetadata(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_marketing_pages_have_seo_tags_and_are_indexable(self):
        for path in MARKETING_PAGES:
            with self.subTest(path=path):
                self.driver.get(BASE_URL + path)
                title = self.driver.title
                self.assertGreater(len(title), 10)

                desc = self.driver.find_element(By.CSS_SELECTOR, 'meta[name="description"]')
                self.assertGreater(len(desc.get_attribute("content")), 20)

                canonical = self.driver.find_element(By.CSS_SELECTOR, 'link[rel="canonical"]')
                self.assertTrue(canonical.get_attribute("href").startswith("https://"))

                og_title = self.driver.find_elements(By.CSS_SELECTOR, 'meta[property="og:title"]')
                og_image = self.driver.find_elements(By.CSS_SELECTOR, 'meta[property="og:image"]')
                self.assertTrue(og_title and og_image, "Missing Open Graph tags")

                robots_tags = self.driver.find_elements(By.CSS_SELECTOR, 'meta[name="robots"]')
                if robots_tags:
                    self.assertNotIn("noindex", robots_tags[0].get_attribute("content"))

    def test_portal_login_is_noindex(self):
        self.driver.get(BASE_URL + "/portal/index.html")
        robots = self.driver.find_element(By.CSS_SELECTOR, 'meta[name="robots"]')
        self.assertIn("noindex", robots.get_attribute("content"))

    def test_home_has_organization_structured_data(self):
        self.driver.get(BASE_URL + "/index.html")
        scripts = self.driver.find_elements(By.CSS_SELECTOR, 'script[type="application/ld+json"]')
        found = False
        for s in scripts:
            data = json.loads(s.get_attribute("innerHTML"))
            if data.get("@type") == "Organization":
                found = True
        self.assertTrue(found)

    def test_faq_has_valid_faqpage_structured_data(self):
        self.driver.get(BASE_URL + "/faq.html")
        scripts = self.driver.find_elements(By.CSS_SELECTOR, 'script[type="application/ld+json"]')
        found = False
        for s in scripts:
            data = json.loads(s.get_attribute("innerHTML"))  # raises if not valid JSON
            if data.get("@type") == "FAQPage":
                self.assertGreaterEqual(len(data.get("mainEntity", [])), 5)
                found = True
        self.assertTrue(found)


class WhatsAppWidget(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def setUp(self):
        self.driver.get(BASE_URL + "/index.html")

    def test_fab_present_and_opens_panel(self):
        fab = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".wa-fab")))
        fab.click()
        panel = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".wa-panel.open")))
        self.assertTrue(panel.is_displayed())

    def test_whatsapp_link_targets_correct_number(self):
        self.driver.find_element(By.CSS_SELECTOR, ".wa-fab").click()
        link = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".wa-action.wa")))
        href = link.get_attribute("href")
        self.assertTrue(href.startswith("https://wa.me/919948039325"))

    def test_call_link_targets_correct_number(self):
        self.driver.find_element(By.CSS_SELECTOR, ".wa-fab").click()
        call_link = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.wa-action[href^="tel:"]')))
        self.assertEqual(call_link.get_attribute("href"), "tel:+919948039325")

    def test_close_button_closes_panel(self):
        self.driver.find_element(By.CSS_SELECTOR, ".wa-fab").click()
        self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".wa-panel.open")))
        self.driver.find_element(By.CSS_SELECTOR, ".wa-close").click()
        time.sleep(0.3)
        panels = self.driver.find_elements(By.CSS_SELECTOR, ".wa-panel.open")
        self.assertEqual(len(panels), 0)


class MobileNavigation(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver(mobile=True)
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_toggle_visible_and_opens_menu(self):
        self.driver.get(BASE_URL + "/index.html")
        toggle = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".nav-toggle")))
        click_safely(self.driver, toggle)
        time.sleep(0.3)
        nav = self.driver.find_element(By.CSS_SELECTOR, ".nav-links")
        self.assertIn("open", nav.get_attribute("class"))

    def test_owner_login_link_never_falsely_active_on_home(self):
        self.driver.get(BASE_URL + "/index.html")
        active_portal_links = self.driver.find_elements(
            By.CSS_SELECTOR, 'a[href="portal/index.html"].active'
        )
        self.assertEqual(len(active_portal_links), 0,
                          "Owner Login link should never carry .active styling on the home page")


class FaqAccordion(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_second_item_opens_and_closes(self):
        self.driver.get(BASE_URL + "/faq.html")
        items = self.driver.find_elements(By.CSS_SELECTOR, ".accordion-item")
        item = items[1]  # item 0 renders pre-opened by design
        self.assertNotIn("open", item.get_attribute("class"))

        click_safely(self.driver, item.find_element(By.CSS_SELECTOR, ".accordion-q"))
        time.sleep(0.3)
        item = self.driver.find_elements(By.CSS_SELECTOR, ".accordion-item")[1]
        self.assertIn("open", item.get_attribute("class"))

        click_safely(self.driver, item.find_element(By.CSS_SELECTOR, ".accordion-q"))
        time.sleep(0.3)
        item = self.driver.find_elements(By.CSS_SELECTOR, ".accordion-item")[1]
        self.assertNotIn("open", item.get_attribute("class"))


class ContactLeadForm(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_form_fields_present_and_required(self):
        self.driver.get(BASE_URL + "/contact.html")
        name = self.driver.find_element(By.ID, "c-name")
        email = self.driver.find_element(By.ID, "c-email")
        self.assertEqual(name.get_attribute("required"), "true")
        self.assertEqual(email.get_attribute("type"), "email")
        self.assertEqual(email.get_attribute("required"), "true")

    @unittest.skipUnless(RUN_LIVE_LEAD_SUBMIT,
                          "Set RUN_LIVE_LEAD_SUBMIT=1 to allow this test to write a real row "
                          "to the live website_leads table. Skipped by default to protect "
                          "production data — see module docstring.")
    def test_submit_shows_success_message_live(self):
        self.driver.get(BASE_URL + "/contact.html")
        self.driver.find_element(By.ID, "c-name").send_keys("Selenium QA Test")
        self.driver.find_element(By.ID, "c-email").send_keys("selenium-qa@example.com")
        self.driver.find_element(By.ID, "c-msg").send_keys("Automated Selenium test submission — safe to ignore/delete.")
        self.driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
        alert = self.wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".form-alert.success"))
        )
        self.assertIn("Thank you", alert.text)


class PortalAuthScreen(unittest.TestCase):
    """
    Read-only checks against the LIVE Supabase project (a getSession() call
    only — no writes, no account created) plus pure client-side UI checks.
    """

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()
        cls.wait = WebDriverWait(cls.driver, 10)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_supabase_client_initializes_and_session_check_completes(self):
        self.driver.get(BASE_URL + "/portal/index.html")
        try:
            self.wait.until(
                lambda d: "display: none" not in
                d.find_element(By.ID, "authArea").get_attribute("style")
            )
        except TimeoutException:
            self.fail("authArea never became visible — Supabase client failed to initialize "
                      "or the session check hung (check for a stale/CDN-blocked supabase-js).")

    def test_register_tab_shows_tier_picker(self):
        self.driver.get(BASE_URL + "/portal/index.html")
        self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.auth-tab[data-tab="register"]'))).click()
        tier_picker = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".tier-picker")))
        self.assertTrue(tier_picker.is_displayed())

    def test_tier_picker_prime_is_selectable(self):
        self.driver.get(BASE_URL + "/portal/index.html")
        self.driver.find_element(By.CSS_SELECTOR, '.auth-tab[data-tab="register"]').click()
        prime = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.tier-opt[data-tier="prime"]')))
        prime.click()
        time.sleep(0.2)
        self.assertIn("selected", prime.get_attribute("class"))

    def test_password_field_is_masked(self):
        self.driver.get(BASE_URL + "/portal/index.html")
        self.driver.find_element(By.CSS_SELECTOR, '.auth-tab[data-tab="register"]').click()
        pw = self.driver.find_element(By.ID, "r-pass")
        self.assertEqual(pw.get_attribute("type"), "password")


class RobotsAndSitemap(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = make_driver()

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

    def test_robots_txt_disallows_dashboard(self):
        self.driver.get(BASE_URL + "/robots.txt")
        body = self.driver.find_element(By.TAG_NAME, "body").text
        self.assertIn("Disallow: /portal/dashboard.html", body)
        self.assertIn("Sitemap:", body)

    def test_sitemap_lists_marketing_pages(self):
        self.driver.get(BASE_URL + "/sitemap.xml")
        body = self.driver.page_source
        self.assertIn("<urlset", body)
        for path in MARKETING_PAGES:
            slug = path.lstrip("/")
            if slug == "index.html":
                self.assertIn("nripropertyguard.in/", body)
            else:
                self.assertIn(slug, body)


if __name__ == "__main__":
    unittest.main(verbosity=2)
