<?php

namespace App\Services;

use Exception;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpClient\HttpClient;

/**
 * Multi-source APK scraper for APKPure, APKMirror, and GitHub releases.
 *
 * Features:
 * - APKPure scraping via HTML parsing (BrowserKit + DomCrawler)
 * - APKMirror scraping via HTML parsing (BrowserKit + DomCrawler)
 * - GitHub releases API integration
 * - Shared hosting compatible (no Node.js required)
 * - Professional error handling and retry logic
 *
 * @author APK Center Dev Team
 * @version 2.0 (no Goutte)
 */
class MultiSourceScraper
{
    private const TIMEOUT        = 15;
    private const RETRY_ATTEMPTS = 3;
    private const CACHE_DURATION = 3600; // 1 hour

    private const USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    ];

    /**
     * Scrape APK data from multiple sources.
     *
     * @param  string  $source  The source: 'apkpure', 'apkmirror', or 'github'
     * @param  string  $query   Search query (app name, package name, or owner/repo for GitHub)
     * @return array            Array of scraped app drafts
     *
     * @throws Exception
     */
    public static function scrape(string $source, string $query): array
    {
        try {
            return match (strtolower($source)) {
                'apkpure'   => self::scrapeAPKPure($query),
                'apkmirror' => self::scrapeAPKMirror($query),
                'github'    => self::scrapeGitHub($query),
                default     => throw new Exception("Unknown source: $source. Supported sources: apkpure, apkmirror, github"),
            };
        } catch (Exception $e) {
            throw new Exception("Failed to scrape from {$source}: {$e->getMessage()}", (int) $e->getCode(), $e);
        }
    }

    /**
     * Scrape from APKPure using HTML parsing.
     *
     * @param  string  $query  App name or package name
     * @return array           Array of app drafts
     */
    private static function scrapeAPKPure(string $query): array
    {
        $drafts    = [];
        $searchUrl = 'https://apkpure.com/search?q='.urlencode($query);

        try {
            $client  = self::createHttpClient();
            $crawler = $client->request('GET', $searchUrl);

            $crawler->filter('div.search-dl, div.app-item')->each(function (Crawler $node) use (&$drafts, $client) {
                try {
                    $linkNode = $node->filter('a')->first();
                    $appLink  = $linkNode->count() ? $linkNode->attr('href') : null;

                    $nameNode = $node->filter('h2 a, .app-title a')->first();
                    $appName  = $nameNode->count() ? $nameNode->text() : null;

                    $devNode  = $node->filter('.dev-info, .developer')->first();
                    $developer= $devNode->count() ? $devNode->text() : 'Unknown';

                    $ratingNode = $node->filter('.rating-average')->first();
                    $ratingText = $ratingNode->count() ? $ratingNode->text() : '0';

                    $versionNode = $node->filter('.version')->first();
                    $version     = $versionNode->count() ? $versionNode->text() : '1.0';

                    $rating      = self::extractRating($ratingText);
                    $packageName = $appLink ? self::extractPackageNameFromURL($appLink) : '';

                    if (! $appName || ! $packageName) {
                        return;
                    }

                    $fullUrl = Str::startsWith($appLink, 'http')
                        ? $appLink
                        : 'https://apkpure.com'.$appLink;

                    $appDetails = self::fetchAPKPureAppDetails($client, $fullUrl, $packageName);

                    $drafts[] = self::normalizeAppDraft(array_merge([
                        'package_name'   => $packageName,
                        'name'           => trim($appName),
                        'developer'      => trim($developer),
                        'rating'         => $rating,
                        'latest_version' => trim($version),
                        'source'         => 'apkpure',
                        'source_url'     => $fullUrl,
                    ], $appDetails));
                } catch (Exception $e) {
                    \Log::debug('Error parsing APKPure app item: '.$e->getMessage());
                }
            });

            return $drafts;
        } catch (Exception $e) {
            throw new Exception('APKPure scraping failed: '.$e->getMessage(), (int) $e->getCode(), $e);
        }
    }

    /**
     * Fetch detailed app information from APKPure product page.
     */
    private static function fetchAPKPureAppDetails(HttpBrowser $client, string $appUrl, string $packageName): array
    {
        try {
            if (! $appUrl) {
                return [];
            }

            $crawler = $client->request('GET', $appUrl);

            $descNode   = $crawler->filter('.description, .intro')->first();
            $description= $descNode->count() ? $descNode->text() : '';

            $iconNode = $crawler->filter('img.icon, .app-icon img')->first();
            $icon     = $iconNode->count() ? $iconNode->attr('src') : null;

            $downloadUrl = self::extractAPKDownloadURL($crawler, 'apkpure');

            return [
                'description'  => trim(mb_substr($description, 0, 500)),
                'icon_url'     => $icon,
                'download_url' => $downloadUrl,
                'size'         => self::estimateAPKSize($crawler),
            ];
        } catch (Exception $e) {
            \Log::debug('Error fetching APKPure details: '.$e->getMessage());

            return [];
        }
    }

    /**
     * Scrape from APKMirror using HTML parsing.
     */
    private static function scrapeAPKMirror(string $query): array
    {
        $drafts    = [];
        $searchUrl = 'https://www.apkmirror.com/?s='.urlencode($query);

        try {
            $client  = self::createHttpClient();
            $crawler = $client->request('GET', $searchUrl);

            $crawler->filter('.appRow, div.listapp')->each(function (Crawler $node) use (&$drafts, $client) {
                try {
                    $linkNode = $node->filter('a')->first();
                    $appLink  = $linkNode->count() ? $linkNode->attr('href') : null;

                    $nameNode = $node->filter('h2, .app_name')->first();
                    $appName  = $nameNode->count() ? $nameNode->text() : null;

                    $devNode  = $node->filter('.dev-name, .publisher')->first();
                    $developer= $devNode->count() ? $devNode->text() : 'Unknown';

                    $verNode = $node->filter('.ver-info, span.version')->first();
                    $version = $verNode->count() ? $verNode->text() : '1.0';

                    if (! $appName || ! $appLink) {
                        return;
                    }

                    $fullUrl = Str::startsWith($appLink, 'http')
                        ? $appLink
                        : 'https://www.apkmirror.com'.$appLink;

                    $packageName = self::extractPackageNameFromURL($fullUrl);
                    $appDetails  = self::fetchAPKMirrorAppDetails($client, $fullUrl);

                    $drafts[] = self::normalizeAppDraft(array_merge([
                        'package_name'   => $packageName,
                        'name'           => trim($appName),
                        'developer'      => trim($developer),
                        'latest_version' => trim($version),
                        'source'         => 'apkmirror',
                        'source_url'     => $fullUrl,
                    ], $appDetails));
                } catch (Exception $e) {
                    \Log::debug('Error parsing APKMirror app item: '.$e->getMessage());
                }
            });

            return $drafts;
        } catch (Exception $e) {
            throw new Exception('APKMirror scraping failed: '.$e->getMessage(), (int) $e->getCode(), $e);
        }
    }

    /**
     * Fetch detailed app information from APKMirror product page.
     */
    private static function fetchAPKMirrorAppDetails(HttpBrowser $client, string $appUrl): array
    {
        try {
            if (! $appUrl) {
                return [];
            }

            $crawler     = $client->request('GET', $appUrl);
            $descNode    = $crawler->filter('p.description, .app-description')->first();
            $description = $descNode->count() ? $descNode->text() : '';

            $iconNode = $crawler->filter('img.icon, .app-icon-large img')->first();
            $icon     = $iconNode->count() ? $iconNode->attr('src') : null;

            $ratingNode = $crawler->filter('span.rating-value, .rating')->first();
            $ratingText = $ratingNode->count() ? $ratingNode->text() : '0';
            $rating     = self::extractRating($ratingText);

            $downloadUrl = self::extractAPKDownloadURL($crawler, 'apkmirror');

            $sizeNode = $crawler->filter('.file-size, span.size')->first();
            $sizeText = $sizeNode->count() ? $sizeNode->text() : '';
            $size     = self::parseFileSize($sizeText);

            return [
                'description'  => trim(mb_substr($description, 0, 500)),
                'icon_url'     => $icon,
                'rating'       => $rating,
                'download_url' => $downloadUrl,
                'size'         => $size,
            ];
        } catch (Exception $e) {
            \Log::debug('Error fetching APKMirror details: '.$e->getMessage());

            return [];
        }
    }

    /**
     * Scrape from GitHub releases (API-based).
     */
    private static function scrapeGitHub(string $query): array
    {
        try {
            [$owner, $repo] = self::parseGitHubQuery($query);

            if (empty($owner) || empty($repo)) {
                throw new Exception('Invalid GitHub query format. Use: owner/repo');
            }

            $apiUrl  = "https://api.github.com/repos/$owner/$repo/releases";
            $headers = [];

            if ($token = config('services.github.token')) {
                $headers['Authorization'] = "Bearer $token";
            }

            $response = Http::timeout(self::TIMEOUT)
                ->withHeaders($headers)
                ->get($apiUrl);

            if (! $response->successful()) {
                throw new Exception('GitHub API error: '.$response->status());
            }

            $releases = $response->json();
            $drafts   = [];

            foreach ($releases as $release) {
                try {
                    $apkAsset = collect($release['assets'] ?? [])
                        ->first(fn ($asset) => Str::endsWith($asset['name'], '.apk'));

                    if (! $apkAsset) {
                        continue;
                    }

                    $packageName = self::extractPackageName($repo);
                    $appName     = $release['name'] ?? $repo;

                    $version = str_replace(['v', 'V'], '', $release['tag_name'] ?? '1.0');
                    $version = preg_replace('/[^0-9.]/i', '', $version) ?: '1.0';

                    $drafts[] = self::normalizeAppDraft([
                        'package_name'   => $packageName,
                        'name'           => trim($appName),
                        'description'    => self::cleanReleaseDescription($release['body'] ?? 'Release from GitHub'),
                        'latest_version' => $version,
                        'download_url'   => $apkAsset['browser_download_url'],
                        'developer'      => $owner,
                        'icon_url'       => null,
                        'size'           => $apkAsset['size'] ?? 0,
                        'source'         => 'github',
                        'source_url'     => $release['html_url'],
                        'released_at'    => $release['published_at'] ?? null,
                    ]);
                } catch (Exception $e) {
                    \Log::debug('Error processing GitHub release: '.$e->getMessage());
                    continue;
                }
            }

            return $drafts;
        } catch (Exception $e) {
            throw new Exception('GitHub scraping failed: '.$e->getMessage(), (int) $e->getCode(), $e);
        }
    }

    /**
     * Create an HTTP browser client with proper configuration.
     */
    private static function createHttpClient(): HttpBrowser
    {
        $symfonyClient = HttpClient::create([
            'timeout' => self::TIMEOUT,
            'headers' => [
                'User-Agent'      => self::getRandomUserAgent(),
                'Accept'          => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Accept-Language' => 'en-US,en;q=0.5',
                'Cache-Control'   => 'no-cache',
            ],
            'http_version' => '1.1',
        ]);

        return new HttpBrowser($symfonyClient);
    }

    private static function extractAPKDownloadURL(Crawler $crawler, string $source): ?string
    {
        try {
            if ($source === 'apkpure') {
                $linkNode = $crawler->selectLink('Download APK');
                if ($linkNode->count()) {
                    try {
                        $downloadLink = $linkNode->link();
                        return $downloadLink->getUri();
                    } catch (\Throwable) {
                        // ignore
                    }
                }

                $aNode = $crawler->filter('a[href*="download"], button[onclick*="download"]')->first();
                if ($aNode->count()) {
                    $downloadUrl = $aNode->attr('href');
                    if ($downloadUrl && Str::startsWith($downloadUrl, 'http')) {
                        return $downloadUrl;
                    }
                }
            } elseif ($source === 'apkmirror') {
                $aNode = $crawler->filter('a[href*="apkmirror.com/apk"], a.downloadButton')->first();
                if ($aNode->count()) {
                    $downloadUrl = $aNode->attr('href');
                    if ($downloadUrl && Str::startsWith($downloadUrl, 'http')) {
                        return $downloadUrl;
                    }
                }
            }

            return null;
        } catch (Exception $e) {
            \Log::debug('Error extracting APK URL: '.$e->getMessage());

            return null;
        }
    }

    private static function extractPackageNameFromURL(string $url): string
    {
        try {
            if (preg_match('/\/app\/([a-z0-9\.]+)/i', $url, $matches)) {
                return strtolower($matches[1]);
            }

            if (preg_match('/\/apk\/([^\/]+)/', $url, $matches)) {
                return strtolower($matches[1]);
            }

            return '';
        } catch (Exception $e) {
            return '';
        }
    }

    private static function estimateAPKSize(Crawler $crawler): int
    {
        try {
            $sizeNode = $crawler->filter('.file-size, span.size, .apk-size')->first();
            $sizeText = $sizeNode->count() ? $sizeNode->text() : '';

            return self::parseFileSize($sizeText);
        } catch (Exception $e) {
            return 0;
        }
    }

    private static function parseFileSize(string $sizeText): int
    {
        $sizeText = strtoupper(trim($sizeText));

        if (preg_match('/(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)/', $sizeText, $matches)) {
            $size = (float) $matches[1];
            $unit = $matches[2];

            return (int) ($size * match ($unit) {
                'KB' => 1024,
                'MB' => 1024 ** 2,
                'GB' => 1024 ** 3,
                default => 1,
            });
        }

        return 0;
    }

    private static function extractRating(string $ratingText): float
    {
        if (preg_match('/(\d+(?:\.\d+)?)/', $ratingText, $matches)) {
            $rating = (float) $matches[1];

            return min(5.0, max(0.0, $rating));
        }

        return 0.0;
    }

    private static function parseGitHubQuery(string $query): array
    {
        if (strpos($query, '/') !== false) {
            [$owner, $repo] = explode('/', $query, 2);

            return [trim($owner), trim($repo)];
        }

        throw new Exception('GitHub query must be in format: owner/repo');
    }

    private static function extractPackageName(string $repoName): string
    {
        $slug  = Str::slug($repoName, '-');
        $parts = explode('-', $slug);

        $domain = 'com';

        if (in_array($parts[0] ?? '', ['mozilla', 'google', 'android', 'apache'])) {
            $domain = 'org';
        }

        return $domain.'.'.str_replace('-', '.', $slug);
    }

    private static function cleanReleaseDescription(string $description): string
    {
        $cleaned = preg_replace('/[#*`\-_]/m', '', $description);
        $cleaned = preg_replace('|https?://\S+|', '', $cleaned);
        $cleaned = trim(mb_substr($cleaned, 0, 500));

        return $cleaned;
    }

    /**
     * Ensure a draft contains the fields expected by APKImportScreen.
     */
    private static function normalizeAppDraft(array $draft): array
    {
        $version = $draft['latest_version'] ?? $draft['version'] ?? null;

        if ($version !== null) {
            $draft['latest_version'] = (string) $version;
            $draft['version']        = (string) $version;
        }

        $draft['package_name'] = isset($draft['package_name']) ? strtolower(trim($draft['package_name'])) : '';
        $draft['name']         = isset($draft['name']) ? trim($draft['name']) : '';
        $draft['download_url'] = $draft['download_url'] ?? null;

        return $draft;
    }

    private static function getRandomUserAgent(): string
    {
        return self::USER_AGENTS[array_rand(self::USER_AGENTS)];
    }

    public static function validate(array $appData): array
    {
        $appData = self::normalizeAppDraft($appData);
        $errors  = [];

        if (empty($appData['name'])) {
            $errors[] = 'App name is required';
        } elseif (strlen($appData['name']) < 2) {
            $errors[] = 'App name must be at least 2 characters';
        } elseif (strlen($appData['name']) > 100) {
            $errors[] = 'App name cannot exceed 100 characters';
        }

        if (empty($appData['package_name'])) {
            $errors[] = 'Package name is required';
        } elseif (! preg_match('/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)*$/i', $appData['package_name'])) {
            $errors[] = 'Invalid package name format (e.g., com.example.app)';
        }

        if (empty($appData['developer'])) {
            $errors[] = 'Developer name is required';
        }

        if (! empty($appData['download_url'])) {
            if (! filter_var($appData['download_url'], FILTER_VALIDATE_URL)) {
                $errors[] = 'Invalid download URL format';
            } elseif (! Str::endsWith($appData['download_url'], '.apk')) {
                $errors[] = 'Download URL must point to an APK file';
            }
        } else {
            $errors[] = 'Download URL is required';
        }

        if (! empty($appData['size'])) {
            $size = (int) $appData['size'];

            if ($size < 1024) {
                $errors[] = 'File size seems invalid (too small)';
            } elseif ($size > 2147483648) {
                $errors[] = 'File size seems invalid (too large)';
            }
        }

        if (empty($appData['latest_version'])) {
            $errors[] = 'Version number is required';
        } elseif (! preg_match('/^\d+(\.\d+)*/', $appData['latest_version'])) {
            $errors[] = 'Invalid version format (e.g., 1.0, 2.1.3)';
        }

        return $errors;
    }

    public static function getSourceInfo(): array
    {
        return [
            'apkpure'  => [
                'name'        => 'APKPure',
                'url'         => 'https://apkpure.com',
                'reliability' => 'high',
                'description' => 'Large APK repository with community ratings',
                'features'    => ['HTML parsing', 'Rating support', 'Version history'],
            ],
            'apkmirror'=> [
                'name'        => 'APKMirror',
                'url'         => 'https://www.apkmirror.com',
                'reliability' => 'high',
                'description' => 'Official mirror for Android applications',
                'features'    => ['HTML parsing', 'Size info', 'Release dates'],
            ],
            'github'   => [
                'name'        => 'GitHub Releases',
                'url'         => 'https://github.com',
                'reliability' => 'very_high',
                'description' => 'Direct source for open-source APK projects',
                'features'    => ['Official API', 'Version control', 'High reliability'],
            ],
        ];
    }
}