<?php

declare(strict_types=1);

namespace App\Framework\Core\Services;

use App\Framework\Core\ValueObjects\AccessibleLink;
use App\Framework\Core\ValueObjects\HtmlLink;
use App\Framework\Core\ValueObjects\LinkRel;
use App\Framework\Core\ValueObjects\LinkTarget;
use App\Framework\Core\ValueObjects\LinkValidationResult;

/**
 * Link Validator Service
 *
 * Validates HTML links for security, accessibility, SEO best practices.
 * Framework-compliant readonly service with composition.
 */
final readonly class LinkValidator
{
    public function __construct(
        private bool $strictMode = false,
        private bool $checkAccessibility = true,
        private bool $checkSeo = true,
    ) {}

    /**
     * Validate HtmlLink
     */
    public function validate(HtmlLink $link): LinkValidationResult
    {
        $errors = [];
        $warnings = [];
        $suggestions = [];

        // Security validation
        $this->validateSecurity($link, $errors, $warnings);

        // Accessibility validation
        if ($this->checkAccessibility) {
            $this->validateAccessibility($link, $warnings, $suggestions);
        }

        // SEO validation
        if ($this->checkSeo) {
            $this->validateSeo($link, $warnings, $suggestions);
        }

        // General best practices
        $this->validateBestPractices($link, $warnings, $suggestions);

        $isValid = empty($errors) && ($this->strictMode ? empty($warnings) : true);

        return new LinkValidationResult(
            isValid: $isValid,
            errors: $errors,
            warnings: $warnings,
            suggestions: $suggestions
        );
    }

    /**
     * Validate AccessibleLink
     */
    public function validateAccessible(AccessibleLink $link): LinkValidationResult
    {
        // First validate base link
        $baseResult = $this->validate($link->baseLink);

        $errors = $baseResult->errors;
        $warnings = $baseResult->warnings;
        $suggestions = $baseResult->suggestions;

        // Additional ARIA validation
        $this->validateAria($link, $warnings, $suggestions);

        $isValid = empty($errors) && ($this->strictMode ? empty($warnings) : true);

        return new LinkValidationResult(
            isValid: $isValid,
            errors: $errors,
            warnings: $warnings,
            suggestions: $suggestions
        );
    }

    /**
     * Validate security aspects
     */
    private function validateSecurity(HtmlLink $link, array &$errors, array &$warnings): void
    {
        // External links with _blank target must have security rels
        if ($link->target === LinkTarget::BLANK && $link->isExternal()) {
            if (!in_array(LinkRel::NOOPENER, $link->rel, true)) {
                $errors[] = 'External link with target="_blank" missing rel="noopener" (security risk: window.opener access)';
            }

            if (!in_array(LinkRel::NOREFERRER, $link->rel, true)) {
                $warnings[] = 'External link with target="_blank" should include rel="noreferrer" for privacy';
            }
        }

        // Download links should have explicit filename
        if ($link->isDownload() && $link->download === '') {
            $warnings[] = 'Download link should specify suggested filename for better UX';
        }

        // JavaScript hrefs are dangerous
        if (is_string($link->href) && str_starts_with(strtolower($link->href), 'javascript:')) {
            $errors[] = 'JavaScript hrefs are security risk - use event handlers instead';
        }
    }

    /**
     * Validate accessibility aspects
     */
    private function validateAccessibility(HtmlLink $link, array &$warnings, array &$suggestions): void
    {
        // Links should have meaningful text
        if ($link->text !== null && strlen($link->text) < 2) {
            $warnings[] = 'Link text too short - use descriptive text for screen readers';
        }

        // Generic link texts
        $genericTexts = ['click here', 'read more', 'here', 'more', 'link'];
        if ($link->text !== null && in_array(strtolower($link->text), $genericTexts, true)) {
            $suggestions[] = "Avoid generic link text '{$link->text}' - use descriptive text that makes sense out of context";
        }

        // External links should indicate they open new window
        if ($link->opensNewWindow() && $link->text !== null) {
            $hasIndicator = str_contains(strtolower($link->text), 'new window')
                || str_contains(strtolower($link->text), 'new tab')
                || $link->title !== null && (
                    str_contains(strtolower($link->title), 'new window')
                    || str_contains(strtolower($link->title), 'new tab')
                );

            if (!$hasIndicator) {
                $suggestions[] = 'Link opens in new window - consider adding aria-label or visible indicator for screen reader users';
            }
        }

        // Disabled links should not be in tab order
        if ($link->disabled) {
            $suggestions[] = 'Consider using AccessibleLink with tabindex="-1" for disabled links to remove from tab order';
        }
    }

    /**
     * Validate ARIA attributes
     */
    private function validateAria(AccessibleLink $link, array &$warnings, array &$suggestions): void
    {
        // aria-label vs visible text
        if ($link->ariaLabel !== null && $link->baseLink->text !== null) {
            if ($link->ariaLabel === $link->baseLink->text) {
                $suggestions[] = 'aria-label duplicates visible text - only use when providing additional context';
            }
        }

        // aria-current should not be used with aria-disabled
        if ($link->ariaCurrent && $link->ariaDisabled) {
            $warnings[] = 'Link marked as both current (aria-current) and disabled (aria-disabled) - conflicting states';
        }

        // aria-hidden links should not be focusable
        if ($link->ariaHidden && $link->tabindex !== -1) {
            $warnings[] = 'aria-hidden link should have tabindex="-1" to remove from tab order';
        }

        // Current page links should not be focusable
        if ($link->ariaCurrent && $link->tabindex !== -1) {
            $suggestions[] = 'Current page link should have tabindex="-1" to remove from keyboard navigation';
        }
    }

    /**
     * Validate SEO aspects
     */
    private function validateSeo(HtmlLink $link, array &$warnings, array &$suggestions): void
    {
        // Internal links should not have nofollow
        if (!$link->isExternal() && in_array(LinkRel::NOFOLLOW, $link->rel, true)) {
            $warnings[] = 'Internal link with rel="nofollow" prevents search engine indexing - usually unintended';
        }

        // Pagination links should use proper rel attributes
        $href = $link->getHref();
        if (preg_match('/[?&]page=\d+/', $href) || preg_match('/\/page\/\d+/', $href)) {
            $paginationRels = [LinkRel::NEXT, LinkRel::PREV, LinkRel::FIRST, LinkRel::LAST];
            $hasPaginationRel = false;

            foreach ($link->rel as $rel) {
                if (in_array($rel, $paginationRels, true)) {
                    $hasPaginationRel = true;
                    break;
                }
            }

            if (!$hasPaginationRel) {
                $suggestions[] = 'Pagination link should use rel="next", rel="prev", rel="first", or rel="last" for SEO';
            }
        }

        // Canonical should be absolute URL
        if (in_array(LinkRel::CANONICAL, $link->rel, true)) {
            if (!$link->isExternal() && !str_starts_with($href, 'http')) {
                $warnings[] = 'Canonical link should use absolute URL for proper SEO';
            }
        }
    }

    /**
     * Validate general best practices
     */
    private function validateBestPractices(HtmlLink $link, array &$warnings, array &$suggestions): void
    {
        // Empty href
        $href = $link->getHref();
        if (trim($href) === '' || $href === '#') {
            $warnings[] = 'Link with empty or hash-only href - consider using button element instead';
        }

        // Links should have text or accessible label
        if ($link->text === null || trim($link->text) === '') {
            $warnings[] = 'Link without text content - ensure aria-label or accessible alternative is provided';
        }

        // Very long link text
        if ($link->text !== null && strlen($link->text) > 100) {
            $suggestions[] = 'Link text is very long (' . strlen($link->text) . ' chars) - consider shortening for better UX';
        }

        // Download links should indicate file type
        if ($link->isDownload() && $link->text !== null) {
            $hasFileType = preg_match('/\.(pdf|doc|docx|xls|xlsx|zip|mp3|mp4)$/i', $link->text);
            if (!$hasFileType) {
                $suggestions[] = 'Download link text should indicate file type (e.g., "Download PDF")';
            }
        }
    }

    /**
     * Batch validate multiple links
     *
     * @param array<HtmlLink|AccessibleLink> $links
     * @return array<LinkValidationResult>
     */
    public function validateBatch(array $links): array
    {
        return array_map(
            fn($link) => $link instanceof AccessibleLink
                ? $this->validateAccessible($link)
                : $this->validate($link),
            $links
        );
    }

    /**
     * Get validation summary for batch
     *
     * @param array<LinkValidationResult> $results
     */
    public function getSummary(array $results): array
    {
        $valid = 0;
        $invalid = 0;
        $totalErrors = 0;
        $totalWarnings = 0;

        foreach ($results as $result) {
            if ($result->isValid) {
                $valid++;
            } else {
                $invalid++;
            }
            $totalErrors += count($result->errors);
            $totalWarnings += count($result->warnings);
        }

        return [
            'total_links' => count($results),
            'valid_links' => $valid,
            'invalid_links' => $invalid,
            'total_errors' => $totalErrors,
            'total_warnings' => $totalWarnings,
            'validation_rate' => count($results) > 0 ? round(($valid / count($results)) * 100, 2) : 0,
        ];
    }
}
