<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects\Services;

use App\Framework\Core\ValueObjects\EmailAddress;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\Core\ValueObjects\HashAlgorithm;

final class EmailSecurityService
{
    public function hashForStorage(EmailAddress $email): Hash
    {
        // Hash normalized email for consistent storage
        return Hash::create($email->normalize()->getValue(), HashAlgorithm::SHA256);
    }

    public function hashForVerification(EmailAddress $email, string $secret): Hash
    {
        $data = $email->normalize()->getValue() . $secret . time();

        return Hash::create($data, HashAlgorithm::SHA256);
    }

    public function generateVerificationToken(EmailAddress $email, string $secret): string
    {
        $hash = $this->hashForVerification($email, $secret);

        return base64_encode($hash->getValue());
    }

    public function verifyToken(EmailAddress $email, string $token, string $secret, int $maxAge = 3600): bool
    {
        try {
            $hash = base64_decode($token, true);
            if ($hash === false) {
                return false;
            }

            // For verification tokens, we'd need to store the timestamp
            // This is a simplified version
            $expectedHash = $this->hashForVerification($email, $secret);

            return hash_equals($expectedHash->getValue(), $hash);
        } catch (\Exception) {
            return false;
        }
    }

    public function detectSimilarEmails(EmailAddress $email, array $existingEmails): array
    {
        $similar = [];
        $normalized = $email->normalize();

        foreach ($existingEmails as $existing) {
            if (! $existing instanceof EmailAddress) {
                continue;
            }

            // Check if emails are similar but not identical
            if (! $normalized->equals($existing->normalize())) {
                $similarity = $this->calculateSimilarity($normalized, $existing);
                if ($similarity > 0.8) {
                    $similar[] = [
                        'email' => $existing,
                        'similarity' => $similarity,
                        'reason' => $this->getSimilarityReason($normalized, $existing),
                    ];
                }
            }
        }

        return $similar;
    }

    public function isSpammy(EmailAddress $email): bool
    {
        $local = $email->getLocalPart();

        // Check for suspicious patterns
        $spamPatterns = [
            '/\d{8,}/',           // Long numbers
            '/noreply|no-reply/', // No-reply addresses
            '/test\d+/',          // Test emails with numbers
            '/admin\d+/',         // Admin emails with numbers
            '/user\d{4,}/',       // User emails with many numbers
        ];

        foreach ($spamPatterns as $pattern) {
            if (preg_match($pattern, strtolower($local))) {
                return true;
            }
        }

        // Check for disposable domains
        if ($email->isDisposable()) {
            return true;
        }

        return false;
    }

    public function getRiskScore(EmailAddress $email): float
    {
        $score = 0.0;

        // Disposable email: high risk
        if ($email->isDisposable()) {
            $score += 0.7;
        }

        // Spammy patterns: medium risk
        if ($this->isSpammy($email)) {
            $score += 0.5;
        }

        // Corporate email: low risk
        if ($email->isCorporate()) {
            $score -= 0.3;
        }

        // Common provider: neutral to low risk
        if ($email->isCommonProvider()) {
            $score -= 0.1;
        }

        return max(0.0, min(1.0, $score));
    }

    private function calculateSimilarity(EmailAddress $email1, EmailAddress $email2): float
    {
        $local1 = $email1->getLocalPart();
        $local2 = $email2->getLocalPart();

        // Levenshtein similarity for local parts
        $maxLength = max(strlen($local1), strlen($local2));
        if ($maxLength === 0) {
            return 1.0;
        }

        $distance = levenshtein($local1, $local2);
        $similarity = 1 - ($distance / $maxLength);

        // Bonus if same domain
        if ($email1->isSameDomain($email2)) {
            $similarity += 0.2;
        }

        return min(1.0, $similarity);
    }

    private function getSimilarityReason(EmailAddress $email1, EmailAddress $email2): string
    {
        if ($email1->isSameDomain($email2)) {
            return 'Same domain with similar local part';
        }

        $local1 = $email1->getLocalPart();
        $local2 = $email2->getLocalPart();

        if (abs(strlen($local1) - strlen($local2)) <= 2) {
            return 'Similar length and characters';
        }

        return 'Similar email structure';
    }
}
