#!/usr/bin/env python3
"""
Audit de un producto BewPro contra el procedimiento canónico de certificación.

Uso:
    python3 scripts/audit-product.py <core-slug>
    python3 scripts/audit-product.py --all

Verifica:
  1. Existen los 4 archivos canónicos del producto (core JSON, config seed, admin forms)
  2. Brand defaults coherentes (no paleta cyberpunk Porto sin ajustar)
  3. Asset pack o fallback bewpro disponible
  4. Paridad blade↔seed↔admin key-by-key (no solo conteo)
  5. CERO hardcoded de marca legacy (Compañía Digital / calendly.lacompaniad / John Doe)
  6. CERO Lorem/Porto/Okler residual
  7. CTA Header en español (no "Donate", "Get a Quote", etc.)
  8. Demo CSS sin hex hardcoded de superficies brand-related
"""

import json
import os
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RESET = '\033[0m'
BOLD = '\033[1m'


def read(path):
    full = ROOT / path
    return full.read_text() if full.exists() else None


def exists(path):
    return (ROOT / path).exists()


# Strings que NO deben aparecer en blades de producción
LEGACY_STRINGS = [
    'Compañía Digital', 'Compania Digital', 'lacompaniad',
    'John Doe', 'Anne Doe', 'Monica Doe', 'Jessica Smith', 'Robert Doe',
    'Porto Theme', 'porto@', 'Okler', 'Lorem ipsum', 'consectetur adipiscing',
    '12345 Porto Blvd',
]

# CTAs en inglés que indican que no se localizó
ENGLISH_CTAS = [
    'Get a Quote', 'Get Started', 'Donate', 'Learn More', 'View More',
    'Read More', 'Featured Projects', 'Impact Numbers', 'What We Do',
    "Let's Go",
]

# Strings genéricos en inglés que aparecen en defaults de helpers
# Si están dentro de __('...') sin config() son hardcoded reales que el cliente NO ve traducido.
ENGLISH_HARDCODED = [
    'Discover what we offer', 'View services', 'View gallery', 'View projects',
    'years of experience', 'projects completed', 'happy clients',
    'Years of Experience', 'Latest Posts', 'Want to know more',
    'About Us', 'Our Mission', 'Our Vision', 'Our Values',
    'Our Expertise', 'What We Built', 'Our Team',
    'Send a Message', 'Contact Us', 'Get In Touch',
    "what we offer", "see our work", "explore our gallery",
]


def audit_product(core_slug):
    print(f"\n{BOLD}═══ AUDIT: {core_slug} ═══{RESET}")
    issues = []
    warns = []

    # 1. Core JSON
    core_path = f'database/seeders/products/core/{core_slug}.json'
    core_raw = read(core_path)
    if not core_raw:
        issues.append(f"❌ Core JSON no existe: {core_path}")
        return issues, warns
    core = json.loads(core_raw)
    demo = core.get('demo', '')
    print(f"  Core JSON: ✅ demo={demo}")

    # 2. CTA Header en español
    cta = core.get('header', {}).get('cta_button', {}).get('title', '')
    if cta in ENGLISH_CTAS:
        issues.append(f"🔴 CTA Header en inglés: '{cta}'")
    else:
        print(f"  CTA Header: ✅ '{cta}'")

    # 3. Brand defaults
    colors = core.get('brand_defaults', {}).get('colors', {})
    primary = colors.get('primary', '')
    cyberpunk_palette = primary in ('#00F0FF',)
    if cyberpunk_palette:
        issues.append(f"🔴 Paleta cyberpunk Porto sin ajustar: primary={primary}")
    else:
        print(f"  Brand: ✅ primary={primary}")

    # 4. Logo pack (existencia)
    logo_pack = core.get('brand_defaults', {}).get('logo_pack', '')
    pack_path = f'public/cd-project/assets/{logo_pack}'
    if exists(pack_path):
        print(f"  Logo pack: ✅ {logo_pack}/")
    elif exists('public/cd-project/assets/bewpro'):
        warns.append(f"⚠️  logo_pack='{logo_pack}' no existe → usa fallback bewpro/")
    else:
        issues.append(f"🔴 logo_pack='{logo_pack}' no existe y no hay fallback bewpro/")

    # 5. Config seed
    seed_path = f'database/seeders/products/core/seeds/config-{core_slug}.json'
    seed_raw = read(seed_path)
    if not seed_raw:
        issues.append(f"🔴 Config seed no existe: {seed_path}")
        return issues, warns
    try:
        seed = json.loads(seed_raw)
    except Exception as e:
        issues.append(f"🔴 Config seed no parsea JSON: {e}")
        return issues, warns
    print(f"  Config seed: ✅ {sum(len(v) if isinstance(v, dict) else 0 for v in seed.values())} keys total")

    # 6. Paridad blade↔seed↔admin key-by-key
    # Keys de _common.blade.php que están disponibles globalmente para todo demo (page_title, page_subtitle, etc.)
    common_contact_raw = read('resources/views/admin/site-data/contact/_common.blade.php') or ''
    common_contact_keys = set(re.findall(r'contact\[([a-z_0-9]+)\]', common_contact_raw))

    for tab in ['welcome', 'about', 'contact']:
        blade_path = f'resources/views/modules/cd-base/frontend/demos/{demo}/{tab}.blade.php'
        blade_raw = read(blade_path)
        if not blade_raw:
            warns.append(f"⚠️  Blade no existe: {blade_path}")
            continue

        # Extraer keys directas (config('site.tab.X')), ignorando incomplete keys
        raw_keys = re.findall(rf'site\.{tab}\.([a-z_0-9]+)', blade_raw)
        blade_keys = set()
        for k in raw_keys:
            if k.endswith('_'):
                continue
            blade_keys.add(k)

        # NUEVO: detectar helper functions tipo $welcome('X'), $about('X'), $contact('X')
        # Estos helpers internamente leen config('site.tab.X', $defaults[X])
        helper_keys = re.findall(rf"\${tab}\(\s*['\"]([a-z_0-9]+)['\"]", blade_raw)
        for k in helper_keys:
            blade_keys.add(k)

        seed_keys = set(seed.get(tab, {}).keys())

        admin_path = f'resources/views/admin/site-data/{tab}/{demo}.blade.php'
        admin_raw = read(admin_path) or ''

        # Extraer inputs y EXPANDIR loops @for($i = 1; $i <= N; $i++) ... name="X[Y_{{ $i }}_Z]"
        admin_inputs = set(re.findall(rf'{tab}\[([a-z_0-9]+)\]', admin_raw))
        # Expandir patrones loop:
        #   name="about[counter_{{ $i }}_value]" con @for($i = 1; $i <= 3; $i++)
        for_blocks = re.finditer(r'@for\s*\(\s*\$i\s*=\s*(\d+)\s*;\s*\$i\s*<=?\s*(\d+)\s*;\s*\$i\s*\+\+\s*\)(.*?)@endfor', admin_raw, re.DOTALL)
        for fb in for_blocks:
            start, end = int(fb.group(1)), int(fb.group(2))
            block = fb.group(3)
            dyn_inputs = re.findall(r'' + tab + r'\[([a-z_0-9]*)\{\{\s*\$i\s*\}\}([a-z_0-9_]*)\]', block)
            for prefix, suffix in dyn_inputs:
                for i in range(start, end + 1):
                    expanded = f"{prefix}{i}{suffix}".strip('_')
                    admin_inputs.add(expanded)
        # Expandir patrones @foreach($X as $key => $label) ... name="X[Y_{{ $key }}_Z]"
        # Detectar arrays @php $X = ['a' => '...', 'b' => '...']; @endphp
        php_arrays = re.finditer(r"@php\s+\$(\w+)\s*=\s*\[([^\]]+)\]\s*;\s*@endphp", admin_raw)
        array_keys_map = {}
        for pa in php_arrays:
            varname = pa.group(1)
            arr_content = pa.group(2)
            # Capturar 'key' => 'value' patterns
            keys = re.findall(r"['\"]([a-z_0-9]+)['\"]\s*=>", arr_content)
            if keys:
                array_keys_map[varname] = keys
        # Buscar @foreach($X as $key => $label) o @foreach($X as $key)
        foreach_blocks = re.finditer(r'@foreach\s*\(\s*\$(\w+)\s+as\s+\$(\w+)(?:\s*=>\s*\$(\w+))?\s*\)(.*?)@endforeach', admin_raw, re.DOTALL)
        for fb in foreach_blocks:
            varname = fb.group(1)
            keyvar = fb.group(2) if fb.group(3) else None
            valvar = fb.group(3) or fb.group(2)
            block = fb.group(4)
            iter_keys = array_keys_map.get(varname, [])
            if not iter_keys:
                continue
            # Extraer inputs con {{ $key }} (donde key es la variable del foreach)
            iter_var = keyvar or valvar
            # Regex: name="tab[prefix{{ $iter_var }}suffix]"
            input_pattern = re.findall(r'' + tab + r'\[([a-z_0-9]*)\{\{\s*\$' + iter_var + r'\s*\}\}([a-z_0-9_]*)\]', block)
            for prefix, suffix in input_pattern:
                for k in iter_keys:
                    expanded = f"{prefix}{k}{suffix}".strip('_')
                    admin_inputs.add(expanded)
        # Inputs de _common (solo para contact)
        if tab == 'contact':
            admin_inputs.update(common_contact_keys)

        missing_seed = blade_keys - seed_keys
        # Para missing_admin, también consideramos que las keys que existen en seed pero no en blade sean OK
        # — pero ese ya se cubre porque solo testeamos blade_keys
        missing_admin = blade_keys - admin_inputs

        if missing_seed:
            issues.append(f"🔴 {tab}: {len(missing_seed)} keys del blade NO están en seed: {sorted(missing_seed)[:5]}")
        if missing_admin:
            issues.append(f"🔴 {tab}: {len(missing_admin)} keys del blade NO editables desde admin: {sorted(missing_admin)[:5]}")
        if not missing_seed and not missing_admin:
            print(f"  Paridad {tab}: ✅ blade={len(blade_keys)} seed={len(seed_keys)} admin={len(admin_inputs)}")

    # 7. Hardcoded legacy strings en blades
    # Considera que strings DENTRO de config('X', __('Y')) son fallback "muertos"
    # (el seed los sobreescribe siempre). Se cuentan SOLO los que aparecen fuera de fallback.
    blade_dir = ROOT / f'resources/views/modules/cd-base/frontend/demos/{demo}'
    if blade_dir.is_dir():
        for blade_file in blade_dir.glob('*.blade.php'):
            content = blade_file.read_text()
            # Quitar todos los fallbacks dentro de config('key', '...') o config('key', __('...'))
            stripped = re.sub(r"config\(\s*['\"][^'\"]+['\"]\s*,\s*(?:__\()?['\"][^'\"]*['\"]\)?\s*\)", "config('K')", content)

            for legacy in LEGACY_STRINGS:
                if legacy in stripped:
                    count = stripped.count(legacy)
                    issues.append(f"🔴 {blade_file.name}: '{legacy}' aparece {count}× hardcoded (fuera de fallback config())")

            for english in ENGLISH_CTAS:
                pattern = rf">{re.escape(english)}<"
                stripped_ctas = re.sub(r"\{\{\s*config\([^)]+\)[^}]*\}\}", "", content)
                stripped_ctas = re.sub(r"\{\{\s*__\([^)]+\)\s*\}\}", "", stripped_ctas)
                if re.search(pattern, stripped_ctas):
                    issues.append(f"🔴 {blade_file.name}: CTA inglés '{english}' hardcoded fuera de config()")

            # NUEVO: detectar strings inglés en helper defaults
            # Patrón: 'key' => __('English text')  — sin lookup a config()
            # Falsos positivos OK porque rompen paridad real
            helper_default_pattern = re.compile(r"['\"]([a-z_0-9]+)['\"]\s*=>\s*__\(\s*['\"]([^'\"]+)['\"]\s*\)")
            for m in helper_default_pattern.finditer(content):
                key, default = m.group(1), m.group(2)
                # Si tiene tildes, probablemente está en español → OK
                if re.search(r'[áéíóúñ¿¡]', default):
                    continue
                # Si aparece en lista de hardcoded conocido → bug
                for english in ENGLISH_HARDCODED:
                    if english.lower() in default.lower():
                        issues.append(f"🔴 {blade_file.name}: helper key '{key}' tiene default inglés hardcoded: \"{default}\"")
                        break

            # NUEVO: detectar strings inglés en TEXTO VISIBLE (entre tags HTML)
            # Quitar bloques @php (donde están los defaults de helpers, ya analizados arriba)
            visible = re.sub(r'@php.*?@endphp', '', content, flags=re.DOTALL)
            visible = re.sub(r'\{\{.*?\}\}', '', visible, flags=re.DOTALL)
            visible = re.sub(r'\{\{--.*?--\}\}', '', visible, flags=re.DOTALL)
            for english in ENGLISH_HARDCODED:
                pattern = rf">\s*{re.escape(english)}\s*[<.]"
                if re.search(pattern, visible, re.I):
                    issues.append(f"🔴 {blade_file.name}: texto inglés visible hardcoded: \"{english}\"")

    # 8. Demo CSS hex hardcoded brand-related
    demo_css = read(f'public/template/css/demos/{demo}.css')
    if demo_css:
        # Extraer hex únicos
        hexes = re.findall(r'#[0-9A-Fa-f]{6}\b', demo_css)
        unique_hex = set(hexes)
        # Filtrar neutros conocidos OK
        neutrals = {'#FFFFFF', '#212529', '#000000', '#555555', '#777777', '#DDDDDD', '#CCCCCC', '#E7E7E7'}
        # Normalizar a uppercase para comparar
        non_neutral = {h.upper() for h in unique_hex} - {n.upper() for n in neutrals}
        # Más utiles cortos
        non_neutral = {h for h in non_neutral if not h.upper().startswith('#FFF') and not h.upper() in ('#555', '#777', '#DDD', '#CCC', '#000')}
        # heuristic: si hay > 5 hexes únicos no-neutrales, probable que haya brand-specific hardcoded
        if len(non_neutral) > 5:
            warns.append(f"⚠️  demo CSS tiene {len(non_neutral)} hex no-neutrales únicos — auditar si son brand-specific")
        else:
            print(f"  Demo CSS: ✅ {len(unique_hex)} hex únicos ({len(non_neutral)} no-neutrales)")

    return issues, warns


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    if sys.argv[1] == '--all':
        # Auditar los 4 certificados
        slugs = ['law-firm-digital', 'construction', 'corporative', 'foundations-ong']
    else:
        slugs = [sys.argv[1]]

    summary = {}
    for slug in slugs:
        issues, warns = audit_product(slug)
        summary[slug] = (issues, warns)

    # Resumen
    print(f"\n\n{BOLD}═══ RESUMEN ═══{RESET}")
    for slug, (issues, warns) in summary.items():
        status = f"{GREEN}✅ CERTIFICADO{RESET}" if not issues else f"{RED}🔴 BUGS DETECTADOS ({len(issues)}){RESET}"
        print(f"\n{BOLD}{slug}{RESET}: {status}")
        for i in issues:
            print(f"  {i}")
        for w in warns:
            print(f"  {YELLOW}{w}{RESET}")


if __name__ == '__main__':
    main()
