<?php
/**
 * SEO Site Auditor - Page Structure Analysis
 * Audits pages for SEO compliance using DOMDocument
 */

class SiteAuditor {
    private $url;
    private $dom;
    private $audit_results;
    private $db;
    
    public function __construct($url, $db = null) {
        $this->url = $url;
        $this->db = $db;
        $this->audit_results = [];
    }
    
    /**
     * Run complete audit on URL
     */
    public function auditPage() {
        $html = $this->fetchPage();
        
        if (!$html) {
            return [
                'success' => false,
                'message' => 'Unable to fetch page content'
            ];
        }
        
        $this->dom = new DOMDocument('1.0', 'UTF-8');
        libxml_use_internal_errors(true);
        $this->dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
        libxml_clear_errors();
        
        return $this->performAudit();
    }
    
    /**
     * Perform comprehensive page audit
     */
    private function performAudit() {
        $audit = [
            'url' => $this->url,
            'audit_date' => date('Y-m-d'),
            'timestamp' => time(),
            'checks' => []
        ];
        
        // Run all checks
        $audit['checks']['h1_check'] = $this->checkH1Tags();
        $audit['checks']['h2_check'] = $this->checkH2Tags();
        $audit['checks']['title_check'] = $this->checkTitle();
        $audit['checks']['meta_desc_check'] = $this->checkMetaDescription();
        $audit['checks']['images_check'] = $this->checkImages();
        $audit['checks']['links_check'] = $this->checkLinks();
        $audit['checks']['breadcrumb_check'] = $this->checkBreadcrumb();
        $audit['checks']['schema_check'] = $this->checkSchema();
        $audit['checks']['accessibility_check'] = $this->checkAccessibility();
        $audit['checks']['mobile_check'] = $this->checkMobileReadiness();
        
        // Calculate scores
        $audit['summary'] = $this->calculateAuditSummary($audit['checks']);
        
        // Save to database if available
        if ($this->db) {
            $this->saveAuditToDatabase($audit);
        }
        
        return [
            'success' => true,
            'audit' => $audit
        ];
    }
    
    /**
     * Check H1 tags - should have exactly one
     */
    private function checkH1Tags() {
        $xpath = new DOMXPath($this->dom);
        $h1_tags = $xpath->query('//h1');
        
        $count = $h1_tags->length;
        $status = 'success';
        $message = 'Perfect: One H1 tag found';
        $severity = 'green';
        
        if ($count === 0) {
            $status = 'error';
            $message = '🔴 FAIL: No H1 tag found. Every page should have exactly one H1.';
            $severity = 'red';
        } elseif ($count > 1) {
            $status = 'warning';
            $message = "🟡 WARNING: $count H1 tags found. Use only one per page.";
            $severity = 'yellow';
        } else {
            $h1_text = trim($h1_tags->item(0)->textContent);
            $message = "🟢 PASS: H1 tag found - \"$h1_text\"";
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'count' => $count,
            'recommendations' => $this->getH1Recommendations($count)
        ];
    }
    
    /**
     * Check H2 tags for content structure
     */
    private function checkH2Tags() {
        $xpath = new DOMXPath($this->dom);
        $h2_tags = $xpath->query('//h2');
        $h3_tags = $xpath->query('//h3');
        
        $h2_count = $h2_tags->length;
        $h3_count = $h3_tags->length;
        
        $status = 'success';
        $message = "Found $h2_count H2 and $h3_count H3 tags";
        $severity = 'green';
        
        if ($h2_count === 0) {
            $status = 'warning';
            $message = '🟡 WARNING: No H2 tags found. Use subheadings to structure your content.';
            $severity = 'yellow';
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'h2_count' => $h2_count,
            'h3_count' => $h3_count,
            'recommendations' => $this->getH2Recommendations($h2_count)
        ];
    }
    
    /**
     * Check page title
     */
    private function checkTitle() {
        $xpath = new DOMXPath($this->dom);
        $title_tags = $xpath->query('//title');
        
        if ($title_tags->length === 0) {
            return [
                'status' => 'error',
                'message' => '🔴 FAIL: No page title tag found',
                'severity' => 'red',
                'title' => '',
                'length' => 0,
                'recommendations' => ['Add a page title (30-60 characters)']
            ];
        }
        
        $title = trim($title_tags->item(0)->textContent);
        $length = strlen($title);
        $status = 'success';
        $severity = 'green';
        $message = "✓ Title: \"$title\"";
        
        $recommendations = [];
        
        if ($length < 30) {
            $status = 'warning';
            $severity = 'yellow';
            $message = "🟡 Title too short ($length chars). Aim for 30-60.";
            $recommendations[] = 'Expand title to 30-60 characters';
        } elseif ($length > 60) {
            $status = 'warning';
            $severity = 'yellow';
            $message = "🟡 Title too long ($length chars). Keep it under 60.";
            $recommendations[] = 'Shorten title to 60 characters or less';
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'title' => $title,
            'length' => $length,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Check meta description
     */
    private function checkMetaDescription() {
        $xpath = new DOMXPath($this->dom);
        $meta_tags = $xpath->query('//meta[@name="description"]');
        
        if ($meta_tags->length === 0) {
            return [
                'status' => 'error',
                'message' => '🔴 FAIL: No meta description found',
                'severity' => 'red',
                'description' => '',
                'length' => 0,
                'recommendations' => ['Add a meta description (120-160 characters)']
            ];
        }
        
        $description = $meta_tags->item(0)->getAttribute('content');
        $length = strlen($description);
        $status = 'success';
        $severity = 'green';
        $message = "✓ Meta description found ($length chars)";
        
        $recommendations = [];
        
        if ($length < 120) {
            $status = 'warning';
            $severity = 'yellow';
            $message = "🟡 Description too short ($length chars). Aim for 120-160.";
            $recommendations[] = 'Expand description to 120-160 characters';
        } elseif ($length > 160) {
            $status = 'warning';
            $severity = 'yellow';
            $message = "🟡 Description too long ($length chars). Keep it under 160.";
            $recommendations[] = 'Shorten description to 160 characters or less';
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'description' => substr($description, 0, 160),
            'length' => $length,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Check images and alt text
     */
    private function checkImages() {
        $xpath = new DOMXPath($this->dom);
        $images = $xpath->query('//img');
        
        $total_images = $images->length;
        $images_without_alt = 0;
        $missing_alt_details = [];
        
        foreach ($images as $img) {
            $alt = $img->getAttribute('alt');
            if (empty(trim($alt))) {
                $images_without_alt++;
                $src = $img->getAttribute('src');
                $missing_alt_details[] = $src ? basename($src) : 'Unknown';
            }
        }
        
        $status = 'success';
        $severity = 'green';
        $message = "✓ All $total_images images have alt text";
        
        $recommendations = [];
        
        if ($images_without_alt > 0) {
            $status = 'warning';
            $severity = 'yellow';
            $message = "🟡 WARNING: $images_without_alt/$total_images images missing alt text";
            $recommendations[] = 'Add descriptive alt text to all images';
        } elseif ($total_images === 0) {
            $status = 'info';
            $message = 'ℹ️ No images found on page';
            $recommendations[] = 'Consider adding relevant images to improve engagement';
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'total_images' => $total_images,
            'images_with_alt' => $total_images - $images_without_alt,
            'images_without_alt' => $images_without_alt,
            'missing_files' => $missing_alt_details,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Check internal and external links
     */
    private function checkLinks() {
        $xpath = new DOMXPath($this->dom);
        $links = $xpath->query('//a[@href]');
        
        $internal_links = 0;
        $external_links = 0;
        $broken_anchors = 0;
        
        $site_domain = parse_url($this->url, PHP_URL_HOST);
        
        foreach ($links as $link) {
            $href = $link->getAttribute('href');
            
            if (empty(trim($link->textContent))) {
                $broken_anchors++;
            }
            
            if (strpos($href, 'http') === 0) {
                $link_domain = parse_url($href, PHP_URL_HOST);
                if ($link_domain === $site_domain) {
                    $internal_links++;
                } else {
                    $external_links++;
                }
            } elseif (strpos($href, '/') === 0 || strpos($href, '#') === 0) {
                $internal_links++;
            }
        }
        
        $status = 'success';
        $severity = 'green';
        $message = "✓ Found $internal_links internal and $external_links external links";
        
        $recommendations = [];
        
        if ($internal_links === 0) {
            $status = 'warning';
            $severity = 'yellow';
            $message = '🟡 No internal links found. Link to other pages on your site.';
            $recommendations[] = 'Add internal links to improve site navigation';
        }
        
        if ($broken_anchors > 0) {
            $recommendations[] = "Fix $broken_anchors broken anchor links (empty text)";
        }
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'internal_links' => $internal_links,
            'external_links' => $external_links,
            'broken_anchors' => $broken_anchors,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Check for breadcrumb schema
     */
    private function checkBreadcrumb() {
        $xpath = new DOMXPath($this->dom);
        
        // Check for breadcrumb class/id
        $breadcrumb_divs = $xpath->query("//*[contains(@class, 'breadcrumb') or contains(@id, 'breadcrumb')]");
        $has_html_breadcrumb = $breadcrumb_divs->length > 0;
        
        // Check for BreadcrumbList schema
        $scripts = $xpath->query('//script[@type="application/ld+json"]');
        $has_schema_breadcrumb = false;
        
        foreach ($scripts as $script) {
            $content = $script->textContent;
            if (strpos($content, 'BreadcrumbList') !== false) {
                $has_schema_breadcrumb = true;
                break;
            }
        }
        
        $status = $has_html_breadcrumb || $has_schema_breadcrumb ? 'success' : 'warning';
        $severity = $has_schema_breadcrumb ? 'green' : 'yellow';
        
        $message = match(true) {
            $has_schema_breadcrumb => '🟢 BreadcrumbList schema found',
            $has_html_breadcrumb => '🟡 HTML breadcrumbs found but no schema',
            default => '🔴 No breadcrumbs detected'
        };
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'html_breadcrumb' => $has_html_breadcrumb,
            'schema_breadcrumb' => $has_schema_breadcrumb,
            'recommendations' => !$has_schema_breadcrumb ? ['Add BreadcrumbList JSON-LD schema'] : []
        ];
    }
    
    /**
     * Check for schema markup
     */
    private function checkSchema() {
        $xpath = new DOMXPath($this->dom);
        $scripts = $xpath->query('//script[@type="application/ld+json"]');
        
        $schema_types = [];
        
        foreach ($scripts as $script) {
            $content = $script->textContent;
            $json = json_decode($content, true);
            
            if ($json && isset($json['@type'])) {
                $schema_types[] = $json['@type'];
            }
        }
        
        $has_schema = count($schema_types) > 0;
        $status = $has_schema ? 'success' : 'warning';
        $severity = $has_schema ? 'green' : 'yellow';
        
        $message = $has_schema 
            ? '🟢 Schema markup found: ' . implode(', ', $schema_types)
            : '🟡 No structured data found. Add schema markup.';
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'schema_count' => count($schema_types),
            'schema_types' => $schema_types,
            'recommendations' => $has_schema ? [] : ['Add JSON-LD schema markup (Article, Product, Organization, etc.)']
        ];
    }
    
    /**
     * Check accessibility
     */
    private function checkAccessibility() {
        $xpath = new DOMXPath($this->dom);
        
        // Check for lang attribute
        $html_element = $xpath->query('//html')->item(0);
        $has_lang = $html_element && $html_element->hasAttribute('lang');
        
        // Check for proper heading hierarchy
        $h1 = $xpath->query('//h1')->length;
        $h2 = $xpath->query('//h2')->length;
        $proper_hierarchy = $h1 > 0;
        
        // Check for form labels
        $inputs = $xpath->query('//input[@type!="hidden"]');
        $labels = $xpath->query('//label');
        
        $recommendations = [];
        $issues = 0;
        
        if (!$has_lang) {
            $recommendations[] = 'Add lang attribute to <html> tag';
            $issues++;
        }
        
        if (!$proper_hierarchy) {
            $recommendations[] = 'Use proper heading hierarchy starting with H1';
            $issues++;
        }
        
        if ($inputs->length > 0 && $labels->length === 0) {
            $recommendations[] = 'Add labels to all form fields';
            $issues++;
        }
        
        $status = $issues === 0 ? 'success' : 'warning';
        $severity = $issues === 0 ? 'green' : 'yellow';
        $message = $issues === 0 ? '🟢 Good accessibility basics' : "🟡 $issues accessibility issues found";
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'issues_found' => $issues,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Check mobile readiness
     */
    private function checkMobileReadiness() {
        $xpath = new DOMXPath($this->dom);
        $metas = $xpath->query('//meta[@name="viewport"]');
        
        $has_viewport = $metas->length > 0;
        
        $recommendations = [];
        if (!$has_viewport) {
            $recommendations[] = 'Add viewport meta tag for mobile optimization';
        }
        
        $status = $has_viewport ? 'success' : 'warning';
        $severity = $has_viewport ? 'green' : 'yellow';
        $message = $has_viewport ? '🟢 Viewport meta tag present' : '🟡 Missing viewport meta tag';
        
        return [
            'status' => $status,
            'message' => $message,
            'severity' => $severity,
            'has_viewport' => $has_viewport,
            'recommendations' => $recommendations
        ];
    }
    
    /**
     * Fetch page content
     */
    private function fetchPage() {
        $context = stream_context_create([
            'http' => [
                'method' => 'GET',
                'timeout' => 10,
                'user_agent' => 'SEO-Auditor/1.0'
            ]
        ]);
        
        $content = @file_get_contents($this->url, false, $context);
        return $content ?: null;
    }
    
    /**
     * Calculate audit summary scores
     */
    private function calculateAuditSummary($checks) {
        $severity_scores = ['green' => 100, 'yellow' => 60, 'red' => 20];
        $total_score = 0;
        $count = 0;
        
        foreach ($checks as $check) {
            if (isset($check['severity'])) {
                $total_score += $severity_scores[$check['severity']] ?? 50;
                $count++;
            }
        }
        
        $average_score = $count > 0 ? round($total_score / $count) : 0;
        
        return [
            'overall_score' => $average_score,
            'grade' => $this->getGrade($average_score),
            'issues_count' => $this->countIssues($checks),
            'timestamp' => time()
        ];
    }
    
    private function getGrade($score) {
        if ($score >= 90) return 'A';
        if ($score >= 80) return 'B';
        if ($score >= 70) return 'C';
        if ($score >= 60) return 'D';
        return 'F';
    }
    
    private function countIssues($checks) {
        $count = 0;
        foreach ($checks as $check) {
            if (isset($check['recommendations']) && count($check['recommendations']) > 0) {
                $count += count($check['recommendations']);
            }
        }
        return $count;
    }
    
    /**
     * Save audit to database
     */
    private function saveAuditToDatabase($audit) {
        if (!$this->db) return false;
        
        $summary = $audit['summary'];
        $checks_json = json_encode($audit['checks']);
        
        $table = 'seo_audit';
        $sql = "INSERT INTO $table 
                (url, audit_date, h1_count, seo_score, audit_details) 
                VALUES (?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE 
                seo_score = VALUES(seo_score),
                audit_details = VALUES(audit_details)";
        
        $stmt = $this->db->prepare($sql);
        if ($stmt) {
            $h1_count = $audit['checks']['h1_check']['count'] ?? 0;
            $score = $summary['overall_score'];
            
            $stmt->bind_param('ssiss', 
                $this->url,
                $audit['audit_date'],
                $h1_count,
                $score,
                $checks_json
            );
            
            return $stmt->execute();
        }
        
        return false;
    }
    
    // Helper methods for recommendations
    private function getH1Recommendations($count) {
        if ($count === 0) return ['Add exactly one H1 tag to your page'];
        if ($count > 1) return ['Remove extra H1 tags. Use H2 or H3 for subheadings'];
        return [];
    }
    
    private function getH2Recommendations($count) {
        if ($count === 0) return ['Add H2 tags to structure your content'];
        return [];
    }
}

// Usage example
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['audit_url'])) {
    $url = filter_var($_POST['audit_url'], FILTER_VALIDATE_URL);
    
    if (!$url) {
        echo json_encode(['success' => false, 'message' => 'Invalid URL']);
        exit;
    }
    
    $auditor = new SiteAuditor($url);
    $result = $auditor->auditPage();
    
    header('Content-Type: application/json');
    echo json_encode($result);
    exit;
}
?>
