<?php

namespace App\Module\Seo\Service;

use App\Shared\Dto\SeoScoreDto;

class SeoScorer
{
    public function analyze(
        string $title,
        ?string $content,
        ?string $seoTitle,
        ?string $seoDescription,
        ?string $focusKeyword,
        ?string $slug,
    ): SeoScoreDto {
        $dto = new SeoScoreDto();

        $this->checkTitleLength($dto, $seoTitle ?? $title);
        $this->checkMetaDescriptionLength($dto, $seoDescription);
        $this->checkFocusKeywordInTitle($dto, $seoTitle ?? $title, $focusKeyword);
        $this->checkFocusKeywordInDescription($dto, $seoDescription, $focusKeyword);
        $this->checkFocusKeywordInUrl($dto, $slug, $focusKeyword);
        $this->checkContentLength($dto, $content);
        $this->checkKeywordDensity($dto, $content, $focusKeyword);
        $this->checkInternalLinks($dto, $content);
        $this->checkExternalLinks($dto, $content);
        $this->checkImageAltText($dto, $content);
        $this->checkHeadingHierarchy($dto, $content);
        $this->checkKeywordInFirstParagraph($dto, $content, $focusKeyword);

        return $dto;
    }

    /**
     * Check 1: title_length (max 10 pts)
     * 50-60 chars = 10pts, 40-70 = 7pts, else 3pts
     */
    private function checkTitleLength(SeoScoreDto $dto, string $title): void
    {
        $length = mb_strlen($title);

        if ($length >= 50 && $length <= 60) {
            $dto->addCheck(
                'title_length',
                'Başlık Uzunluğu',
                'success',
                "Başlık uzunluğu ideal ({$length} karakter). 50-60 karakter arasında.",
                10,
            );
        } elseif ($length >= 40 && $length <= 70) {
            $dto->addCheck(
                'title_length',
                'Başlık Uzunluğu',
                'warning',
                "Başlık uzunluğu kabul edilebilir ({$length} karakter) ancak 50-60 karakter arası daha iyi olur.",
                7,
            );
        } else {
            $dto->addCheck(
                'title_length',
                'Başlık Uzunluğu',
                'error',
                "Başlık uzunluğu uygun değil ({$length} karakter). 50-60 karakter arası olmalıdır.",
                3,
            );
        }
    }

    /**
     * Check 2: meta_description_length (max 10 pts)
     * 120-160 chars = 10pts, 80-200 = 7pts, else 3pts
     */
    private function checkMetaDescriptionLength(SeoScoreDto $dto, ?string $seoDescription): void
    {
        if ($seoDescription === null || $seoDescription === '') {
            $dto->addCheck(
                'meta_description_length',
                'Meta Açıklama Uzunluğu',
                'error',
                'Meta açıklama tanımlanmamış. 120-160 karakter arası bir açıklama ekleyin.',
                3,
            );

            return;
        }

        $length = mb_strlen($seoDescription);

        if ($length >= 120 && $length <= 160) {
            $dto->addCheck(
                'meta_description_length',
                'Meta Açıklama Uzunluğu',
                'success',
                "Meta açıklama uzunluğu ideal ({$length} karakter). 120-160 karakter arasında.",
                10,
            );
        } elseif ($length >= 80 && $length <= 200) {
            $dto->addCheck(
                'meta_description_length',
                'Meta Açıklama Uzunluğu',
                'warning',
                "Meta açıklama uzunluğu kabul edilebilir ({$length} karakter) ancak 120-160 karakter arası daha iyi olur.",
                7,
            );
        } else {
            $dto->addCheck(
                'meta_description_length',
                'Meta Açıklama Uzunluğu',
                'error',
                "Meta açıklama uzunluğu uygun değil ({$length} karakter). 120-160 karakter arası olmalıdır.",
                3,
            );
        }
    }

    /**
     * Check 3: focus_keyword_in_title (max 10 pts)
     */
    private function checkFocusKeywordInTitle(SeoScoreDto $dto, string $title, ?string $focusKeyword): void
    {
        if ($focusKeyword === null || $focusKeyword === '') {
            $dto->addCheck(
                'focus_keyword_in_title',
                'Odak Anahtar Kelime - Başlık',
                'error',
                'Odak anahtar kelime belirlenmemiş. SEO için bir anahtar kelime belirleyin.',
                0,
            );

            return;
        }

        $found = mb_stripos($title, $focusKeyword) !== false;

        if ($found) {
            $dto->addCheck(
                'focus_keyword_in_title',
                'Odak Anahtar Kelime - Başlık',
                'success',
                'Odak anahtar kelime başlıkta bulundu.',
                10,
            );
        } else {
            $dto->addCheck(
                'focus_keyword_in_title',
                'Odak Anahtar Kelime - Başlık',
                'error',
                'Odak anahtar kelime başlıkta bulunamadı. Başlığa anahtar kelimeyi ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 4: focus_keyword_in_description (max 8 pts)
     */
    private function checkFocusKeywordInDescription(SeoScoreDto $dto, ?string $seoDescription, ?string $focusKeyword): void
    {
        if ($focusKeyword === null || $focusKeyword === '') {
            $dto->addCheck(
                'focus_keyword_in_description',
                'Odak Anahtar Kelime - Meta Açıklama',
                'error',
                'Odak anahtar kelime belirlenmemiş.',
                0,
            );

            return;
        }

        if ($seoDescription === null || $seoDescription === '') {
            $dto->addCheck(
                'focus_keyword_in_description',
                'Odak Anahtar Kelime - Meta Açıklama',
                'error',
                'Meta açıklama tanımlanmamış. Anahtar kelimeyi içeren bir meta açıklama ekleyin.',
                0,
            );

            return;
        }

        $found = mb_stripos($seoDescription, $focusKeyword) !== false;

        if ($found) {
            $dto->addCheck(
                'focus_keyword_in_description',
                'Odak Anahtar Kelime - Meta Açıklama',
                'success',
                'Odak anahtar kelime meta açıklamada bulundu.',
                8,
            );
        } else {
            $dto->addCheck(
                'focus_keyword_in_description',
                'Odak Anahtar Kelime - Meta Açıklama',
                'error',
                'Odak anahtar kelime meta açıklamada bulunamadı. Meta açıklamaya anahtar kelimeyi ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 5: focus_keyword_in_url (max 7 pts)
     */
    private function checkFocusKeywordInUrl(SeoScoreDto $dto, ?string $slug, ?string $focusKeyword): void
    {
        if ($focusKeyword === null || $focusKeyword === '') {
            $dto->addCheck(
                'focus_keyword_in_url',
                'Odak Anahtar Kelime - URL',
                'error',
                'Odak anahtar kelime belirlenmemiş.',
                0,
            );

            return;
        }

        if ($slug === null || $slug === '') {
            $dto->addCheck(
                'focus_keyword_in_url',
                'Odak Anahtar Kelime - URL',
                'error',
                'URL (slug) tanımlanmamış. Anahtar kelimeyi içeren bir URL belirleyin.',
                0,
            );

            return;
        }

        // Normalize: convert keyword to slug-like format for comparison
        $normalizedKeyword = mb_strtolower($focusKeyword);
        $normalizedSlug = mb_strtolower($slug);

        // Check both direct match and slug-formatted match (spaces replaced with hyphens)
        $slugKeyword = str_replace(' ', '-', $normalizedKeyword);

        $found = mb_strpos($normalizedSlug, $normalizedKeyword) !== false
            || mb_strpos($normalizedSlug, $slugKeyword) !== false;

        if ($found) {
            $dto->addCheck(
                'focus_keyword_in_url',
                'Odak Anahtar Kelime - URL',
                'success',
                'Odak anahtar kelime URL\'de bulundu.',
                7,
            );
        } else {
            $dto->addCheck(
                'focus_keyword_in_url',
                'Odak Anahtar Kelime - URL',
                'error',
                'Odak anahtar kelime URL\'de bulunamadı. URL\'ye anahtar kelimeyi ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 6: content_length (max 10 pts)
     * 300+ words = 10pts, 150+ = 7pts, else 3pts
     */
    private function checkContentLength(SeoScoreDto $dto, ?string $content): void
    {
        if ($content === null || $content === '') {
            $dto->addCheck(
                'content_length',
                'İçerik Uzunluğu',
                'error',
                'İçerik bulunamadı. SEO için en az 300 kelimelik içerik oluşturun.',
                3,
            );

            return;
        }

        $plainText = strip_tags($content);
        $wordCount = str_word_count($plainText, 0, 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĞğİıŞşÜüÖöÇç');

        if ($wordCount >= 300) {
            $dto->addCheck(
                'content_length',
                'İçerik Uzunluğu',
                'success',
                "İçerik uzunluğu ideal ({$wordCount} kelime). 300 kelimenin üzerinde.",
                10,
            );
        } elseif ($wordCount >= 150) {
            $dto->addCheck(
                'content_length',
                'İçerik Uzunluğu',
                'warning',
                "İçerik uzunluğu kabul edilebilir ({$wordCount} kelime) ancak 300 kelimenin üzerinde olması daha iyi olur.",
                7,
            );
        } else {
            $dto->addCheck(
                'content_length',
                'İçerik Uzunluğu',
                'error',
                "İçerik çok kısa ({$wordCount} kelime). En az 300 kelimelik içerik oluşturun.",
                3,
            );
        }
    }

    /**
     * Check 7: keyword_density (max 8 pts)
     * 1-3% = 8pts, 0.5-4% = 5pts, else 2pts
     */
    private function checkKeywordDensity(SeoScoreDto $dto, ?string $content, ?string $focusKeyword): void
    {
        if ($focusKeyword === null || $focusKeyword === '') {
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'error',
                'Odak anahtar kelime belirlenmemiş. Yoğunluk hesaplanamadı.',
                2,
            );

            return;
        }

        if ($content === null || $content === '') {
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'error',
                'İçerik bulunamadı. Anahtar kelime yoğunluğu hesaplanamadı.',
                2,
            );

            return;
        }

        $plainText = mb_strtolower(strip_tags($content));
        $keyword = mb_strtolower($focusKeyword);

        $wordCount = str_word_count($plainText, 0, 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĞğİıŞşÜüÖöÇç');

        if ($wordCount === 0) {
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'error',
                'İçerikte kelime bulunamadı.',
                2,
            );

            return;
        }

        $keywordCount = mb_substr_count($plainText, $keyword);
        $keywordWordCount = str_word_count($keyword, 0, 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĞğİıŞşÜüÖöÇç');
        $density = ($keywordCount * $keywordWordCount / $wordCount) * 100;
        $densityFormatted = number_format($density, 1);

        if ($density >= 1.0 && $density <= 3.0) {
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'success',
                "Anahtar kelime yoğunluğu ideal (%{$densityFormatted}). %1-3 arasında.",
                8,
            );
        } elseif ($density >= 0.5 && $density <= 4.0) {
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'warning',
                "Anahtar kelime yoğunluğu kabul edilebilir (%{$densityFormatted}) ancak %1-3 arası daha iyi olur.",
                5,
            );
        } else {
            $status = $density < 0.5 ? 'çok düşük' : 'çok yüksek';
            $dto->addCheck(
                'keyword_density',
                'Anahtar Kelime Yoğunluğu',
                'error',
                "Anahtar kelime yoğunluğu {$status} (%{$densityFormatted}). %1-3 arası olmalıdır.",
                2,
            );
        }
    }

    /**
     * Check 8: internal_links (max 7 pts)
     * Content contains <a links
     */
    private function checkInternalLinks(SeoScoreDto $dto, ?string $content): void
    {
        if ($content === null || $content === '') {
            $dto->addCheck(
                'internal_links',
                'Dahili Bağlantılar',
                'error',
                'İçerik bulunamadı. İçeriğe dahili bağlantılar ekleyin.',
                0,
            );

            return;
        }

        $hasLinks = stripos($content, '<a') !== false;

        if ($hasLinks) {
            $dto->addCheck(
                'internal_links',
                'Dahili Bağlantılar',
                'success',
                'İçerikte bağlantılar bulundu.',
                7,
            );
        } else {
            $dto->addCheck(
                'internal_links',
                'Dahili Bağlantılar',
                'error',
                'İçerikte hiç bağlantı bulunamadı. SEO için dahili bağlantılar ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 9: external_links (max 5 pts)
     * Content contains links with http
     */
    private function checkExternalLinks(SeoScoreDto $dto, ?string $content): void
    {
        if ($content === null || $content === '') {
            $dto->addCheck(
                'external_links',
                'Harici Bağlantılar',
                'error',
                'İçerik bulunamadı. İçeriğe harici bağlantılar ekleyin.',
                0,
            );

            return;
        }

        // Look for anchor tags containing http(s) URLs
        $hasExternalLinks = (bool) preg_match('/<a\s[^>]*href\s*=\s*["\']https?:\/\//i', $content);

        if ($hasExternalLinks) {
            $dto->addCheck(
                'external_links',
                'Harici Bağlantılar',
                'success',
                'İçerikte harici bağlantılar bulundu.',
                5,
            );
        } else {
            $dto->addCheck(
                'external_links',
                'Harici Bağlantılar',
                'warning',
                'İçerikte harici bağlantı bulunamadı. Güvenilir kaynaklara harici bağlantılar ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 10: image_alt_text (max 8 pts)
     * Images in content have alt= attributes
     */
    private function checkImageAltText(SeoScoreDto $dto, ?string $content): void
    {
        if ($content === null || $content === '') {
            $dto->addCheck(
                'image_alt_text',
                'Görsel Alt Metni',
                'warning',
                'İçerik bulunamadı. Görseller ekleyip alt metni tanımlayın.',
                0,
            );

            return;
        }

        // Find all img tags
        preg_match_all('/<img\s[^>]*>/i', $content, $imgMatches);

        if (empty($imgMatches[0])) {
            $dto->addCheck(
                'image_alt_text',
                'Görsel Alt Metni',
                'warning',
                'İçerikte görsel bulunamadı. SEO için görseller ekleyin ve alt metin tanımlayın.',
                0,
            );

            return;
        }

        $totalImages = count($imgMatches[0]);
        $imagesWithAlt = 0;

        foreach ($imgMatches[0] as $imgTag) {
            // Check for alt attribute with non-empty value
            if (preg_match('/alt\s*=\s*["\'][^"\']+["\']/i', $imgTag)) {
                $imagesWithAlt++;
            }
        }

        if ($imagesWithAlt === $totalImages) {
            $dto->addCheck(
                'image_alt_text',
                'Görsel Alt Metni',
                'success',
                "Tüm görsellerde alt metin tanımlı ({$totalImages}/{$totalImages}).",
                8,
            );
        } elseif ($imagesWithAlt > 0) {
            $dto->addCheck(
                'image_alt_text',
                'Görsel Alt Metni',
                'warning',
                "Bazı görsellerde alt metin eksik ({$imagesWithAlt}/{$totalImages}). Tüm görsellere alt metin ekleyin.",
                4,
            );
        } else {
            $dto->addCheck(
                'image_alt_text',
                'Görsel Alt Metni',
                'error',
                "Hiçbir görselde alt metin tanımlı değil (0/{$totalImages}). Tüm görsellere alt metin ekleyin.",
                0,
            );
        }
    }

    /**
     * Check 11: heading_hierarchy (max 8 pts)
     * Content uses H2/H3 headings
     */
    private function checkHeadingHierarchy(SeoScoreDto $dto, ?string $content): void
    {
        if ($content === null || $content === '') {
            $dto->addCheck(
                'heading_hierarchy',
                'Başlık Hiyerarşisi',
                'error',
                'İçerik bulunamadı. İçeriğe H2 ve H3 başlıkları ekleyin.',
                0,
            );

            return;
        }

        $hasH2 = (bool) preg_match('/<h2[\s>]/i', $content);
        $hasH3 = (bool) preg_match('/<h3[\s>]/i', $content);

        if ($hasH2 && $hasH3) {
            $dto->addCheck(
                'heading_hierarchy',
                'Başlık Hiyerarşisi',
                'success',
                'İçerikte H2 ve H3 başlıkları kullanılmış. Başlık hiyerarşisi doğru.',
                8,
            );
        } elseif ($hasH2 || $hasH3) {
            $found = $hasH2 ? 'H2' : 'H3';
            $missing = $hasH2 ? 'H3' : 'H2';
            $dto->addCheck(
                'heading_hierarchy',
                'Başlık Hiyerarşisi',
                'warning',
                "İçerikte yalnızca {$found} başlıkları var. Daha iyi bir hiyerarşi için {$missing} başlıkları da ekleyin.",
                5,
            );
        } else {
            $dto->addCheck(
                'heading_hierarchy',
                'Başlık Hiyerarşisi',
                'error',
                'İçerikte H2/H3 başlık bulunamadı. İçerik yapısını iyileştirmek için alt başlıklar ekleyin.',
                0,
            );
        }
    }

    /**
     * Check 12: keyword_in_first_paragraph (max 9 pts)
     * Focus keyword in first 200 chars of content
     */
    private function checkKeywordInFirstParagraph(SeoScoreDto $dto, ?string $content, ?string $focusKeyword): void
    {
        if ($focusKeyword === null || $focusKeyword === '') {
            $dto->addCheck(
                'keyword_in_first_paragraph',
                'İlk Paragrafta Anahtar Kelime',
                'error',
                'Odak anahtar kelime belirlenmemiş.',
                0,
            );

            return;
        }

        if ($content === null || $content === '') {
            $dto->addCheck(
                'keyword_in_first_paragraph',
                'İlk Paragrafta Anahtar Kelime',
                'error',
                'İçerik bulunamadı.',
                0,
            );

            return;
        }

        $plainText = strip_tags($content);
        $first200 = mb_substr($plainText, 0, 200);

        $found = mb_stripos($first200, $focusKeyword) !== false;

        if ($found) {
            $dto->addCheck(
                'keyword_in_first_paragraph',
                'İlk Paragrafta Anahtar Kelime',
                'success',
                'Odak anahtar kelime içeriğin ilk 200 karakterinde bulundu.',
                9,
            );
        } else {
            $dto->addCheck(
                'keyword_in_first_paragraph',
                'İlk Paragrafta Anahtar Kelime',
                'error',
                'Odak anahtar kelime içeriğin başında bulunamadı. Anahtar kelimeyi ilk paragrafa ekleyin.',
                0,
            );
        }
    }
}
