#!/usr/bin/env python3

import argparse
import sys
import json
from datetime import date
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

USER_AGENT = "LKP-Validator/0.1.0 (+https://livingknowledgeprotocol.org/sdk/)"
REQUEST_TIMEOUT = 10  # secondes


def validate_lkp(source):
    """Valide une source (URL ou fichier) pour la conformité LKP.

    Retourne True si toutes les vérifications passent, False sinon.
    """
    print(f"\n--- LKP Validation Report for: {source} ---")

    fail_count = 0
    html_content = ""
    base_url = None

    if source.startswith("http"):
        try:
            headers = {"User-Agent": USER_AGENT}
            response = requests.get(source, headers=headers, timeout=REQUEST_TIMEOUT)
            response.raise_for_status()
            html_content = response.text
            base_url = urljoin(source, "/")
            print("Source Type: URL")
        except requests.exceptions.RequestException as e:
            print(f"Error fetching URL: {e}")
            return False
    else:
        try:
            with open(source, "r", encoding="utf-8") as f:
                html_content = f.read()
            print("Source Type: Local File")
        except FileNotFoundError:
            print(f"Error: File not found at {source}")
            return False
        except Exception as e:
            print(f"Error reading file: {e}")
            return False

    soup = BeautifulSoup(html_content, "html.parser")

    # 1. Verification des Meta Tags LKP
    print("\n--- Meta Tags LKP ---")
    lkp_meta_tags = {}
    for meta in soup.find_all("meta", attrs={"name": lambda x: x and x.startswith("lkp-")}):
        name = meta.get("name")
        meta_content = meta.get("content")  # ne collisionne plus avec html_content
        lkp_meta_tags[name] = meta_content
        print(f"  {name}: {meta_content}")

    if not lkp_meta_tags:
        print("  No LKP meta tags found. (FAIL)")
        fail_count += 1
    else:
        print("  LKP meta tags found. (PASS)")

    # 2. Verification du lkp.json Manifest
    print("\n--- lkp.json Manifest ---")
    lkp_json_content = None
    lkp_json_location = ""

    if source.startswith("http"):
        # Comportement documente : lkp.json est cherche a la racine du domaine,
        # pas dans le meme dossier que la page validee (contrairement au mode fichier local).
        lkp_json_location = urljoin(base_url, "lkp.json")
        try:
            headers = {"User-Agent": USER_AGENT}
            response = requests.get(lkp_json_location, headers=headers, timeout=REQUEST_TIMEOUT)
            response.raise_for_status()
            lkp_json_content = response.json()
            print(f"  lkp.json found at {lkp_json_location} (PASS)")
        except requests.exceptions.RequestException:
            print(f"  lkp.json not found at {lkp_json_location} (FAIL)")
            fail_count += 1
        except json.JSONDecodeError:
            print(f"  lkp.json at {lkp_json_location} is not valid JSON (FAIL)")
            fail_count += 1
    else:
        lkp_json_location = source.rsplit("/", 1)[0] + "/lkp.json" if "/" in source else "lkp.json"
        try:
            with open(lkp_json_location, "r", encoding="utf-8") as f:
                lkp_json_content = json.load(f)
            print(f"  lkp.json found at {lkp_json_location} (PASS)")
        except FileNotFoundError:
            print(f"  lkp.json not found at {lkp_json_location} (FAIL)")
            fail_count += 1
        except json.JSONDecodeError:
            print(f"  lkp.json at {lkp_json_location} is not valid JSON (FAIL)")
            fail_count += 1
        except Exception as e:
            print(f"  Error reading lkp.json: {e} (FAIL)")
            fail_count += 1

    if lkp_json_content:
        required_fields = ["protocol", "version", "implementation", "conformance"]
        missing_fields = [f for f in required_fields if f not in lkp_json_content]
        if missing_fields:
            print(f"  Missing required fields in lkp.json: {', '.join(missing_fields)} (FAIL)")
            fail_count += 1
        else:
            print("  lkp.json has all required fields. (PASS)")
            print(f"    Protocol Version: {lkp_json_content.get('version')}")
            print(f"    Implementation Status: {lkp_json_content.get('implementation', {}).get('status')}")

    # 3. Verification du JSON-LD
    print("\n--- JSON-LD Metadata ---")
    CURRENT_NS = "https://livingknowledgeprotocol.org/ns/1.0.0"
    LEGACY_NS = "https://livingknowledgeprotocol.org/ns/1.0"
    LEGACY_NS_DEADLINE = date(2026, 12, 31)  # cf. LKP-0003 3.3
    today = date.today()

    json_ld_scripts = soup.find_all("script", type="application/ld+json")
    found_lkp_json_ld = False
    for script in json_ld_scripts:
        try:
            data = json.loads(script.string)
            context = data.get("@context")
            if context not in (CURRENT_NS, LEGACY_NS):
                continue

            # Format A (recommended): a real schema.org type at the top level
            # (e.g. "WebPage"), carrying LKP data in a nested "lkp" object
            # whose own @type is "LivingDocument". This is the format used by
            # the reference implementation, and is preferred because it stays
            # compatible with standard schema.org consumers (e.g. Google).
            lkp_obj = data.get("lkp")
            is_nested_format = isinstance(lkp_obj, dict) and lkp_obj.get("@type") == "LivingDocument"

            # Format B (legacy/alternate): "LivingDocument" used directly as
            # the top-level @type, as originally shown in early LKP-0003
            # examples. Still accepted for backward compatibility with early
            # adopters who implemented it this way.
            is_toplevel_format = data.get("@type") == "LivingDocument"

            if not (is_nested_format or is_toplevel_format):
                continue

            found_lkp_json_ld = True
            is_legacy_ns = context == LEGACY_NS

            if is_legacy_ns and today > LEGACY_NS_DEADLINE:
                print("  LKP LivingDocument JSON-LD found, but using a namespace that is no longer supported. (FAIL)")
                print(f"    Current namespace: {LEGACY_NS}")
                print(f"    Legacy namespace support ended {LEGACY_NS_DEADLINE.isoformat()}.")
                print(f"    You MUST update to: {CURRENT_NS}")
                fail_count += 1
            elif is_legacy_ns:
                days_left = (LEGACY_NS_DEADLINE - today).days
                print("  LKP LivingDocument JSON-LD found, but using a LEGACY namespace. (WARN)")
                print(f"    Current namespace: {LEGACY_NS}")
                print(f"    Please update to: {CURRENT_NS}")
                print(f"    Legacy namespace support ends {LEGACY_NS_DEADLINE.isoformat()} ({days_left} day(s) left).")
            else:
                print("  LKP LivingDocument JSON-LD found. (PASS)")

            if is_nested_format:
                print(f"    Schema Format: Nested (@type: {data.get('@type')} > lkp.@type: LivingDocument)")
                print(f"    Document Name: {data.get('name')}")
                print(f"    Status: {lkp_obj.get('status')}")
                print(f"    Date Modified: {lkp_obj.get('dateModified')}")
            else:
                print("    Schema Format: Top-level LivingDocument")
                print(f"    Document Name: {data.get('name')}")
                print(f"    Date Modified: {data.get('dateModified')}")
                print(f"    Compliance Level: {data.get('lkp', {}).get('complianceLevel')}")
            break
        except json.JSONDecodeError:
            continue

    if not found_lkp_json_ld:
        print("  No LKP LivingDocument JSON-LD found. (FAIL)")
        fail_count += 1

    print("\n--- Validation Complete ---")
    if fail_count > 0:
        print(f"Result: {fail_count} check(s) FAILED.\n")
    else:
        print("Result: All checks PASSED.\n")

    return fail_count == 0


def main():
    parser = argparse.ArgumentParser(description="LKP CLI: Validate Living Knowledge Protocol compliance.")
    parser.add_argument("source", help="URL or local file path to validate.")
    args = parser.parse_args()

    is_valid = validate_lkp(args.source)

    # Code de sortie non-nul en cas d'echec : necessaire pour une integration CI/CD.
    sys.exit(0 if is_valid else 1)


if __name__ == "__main__":
    main()