<?php

declare(strict_types=1);

namespace App\Services;

use App\Helpers\Logger;
use App\Models\SiteSeoMetric;

class SiteSeoSyncService
{
    private SemstormService $semstorm;
    private DataForSeoService $dataforseo;

    public function __construct()
    {
        $this->semstorm = new SemstormService();
        $this->dataforseo = new DataForSeoService();
    }

    public function syncSite(array $site, ?\DateTimeImmutable $month = null, bool $force = false): array
    {
        $siteId = (int) ($site['id'] ?? 0);
        if ($siteId <= 0) {
            return ['success' => false, 'status' => 'error', 'message' => 'Nieprawidlowe site_id.'];
        }

        $metricMonth = ($month ?? new \DateTimeImmutable('first day of this month'))->format('Y-m-01');
        $existing = SiteSeoMetric::findForMonth($siteId, $metricMonth);
        $hasMonth = $existing !== null;
        $hasDataforseo = SiteSeoMetric::hasDataforseoForMonth($siteId, $metricMonth);

        if (!$force && $hasMonth && $hasDataforseo) {
            return [
                'success' => true,
                'status' => 'skipped',
                'message' => 'Dane SEO dla tego miesiaca juz istnieja (SEMSTORM + DataForSEO).',
                'metric_month' => $metricMonth,
            ];
        }

        $metricsToSave = [
            'top3' => (int) ($existing['top3'] ?? 0),
            'top10' => (int) ($existing['top10'] ?? 0),
            'top20' => (int) ($existing['top20'] ?? 0),
            'top50' => (int) ($existing['top50'] ?? 0),
            'traffic' => (int) ($existing['traffic'] ?? 0),
            'indexed_pages' => (int) ($existing['indexed_pages'] ?? 0),
        ];

        $payload = $this->extractPayloadParts($existing['source_payload'] ?? null);
        $semstormDomain = $this->resolveDomain($site);
        $dataforseoDomain = $this->resolveDataforseoDomain($site, $semstormDomain);
        $errors = [];
        $didSync = false;

        try {
            if ($force || !$hasMonth) {
                if ($semstormDomain !== '') {
                    $semstormMetrics = $this->semstorm->fetchDomainMetrics($semstormDomain, new \DateTimeImmutable($metricMonth));
                    $metricsToSave['top3'] = (int) ($semstormMetrics['top3'] ?? 0);
                    $metricsToSave['top10'] = (int) ($semstormMetrics['top10'] ?? 0);
                    $metricsToSave['top20'] = (int) ($semstormMetrics['top20'] ?? 0);
                    $metricsToSave['top50'] = (int) ($semstormMetrics['top50'] ?? 0);
                    $metricsToSave['traffic'] = (int) ($semstormMetrics['traffic'] ?? 0);
                    $payload['semstorm'] = is_string($semstormMetrics['payload'] ?? null) ? $semstormMetrics['payload'] : null;
                    $didSync = true;
                } else {
                    $errors[] = 'Brak domeny SEMSTORM.';
                }
            }

            if ($force || !$hasDataforseo) {
                if ($dataforseoDomain !== '') {
                    $dataforseo = $this->dataforseo->fetchIndexedPagesCount($dataforseoDomain);
                    $metricsToSave['indexed_pages'] = max(0, (int) ($dataforseo['indexed_pages'] ?? 0));
                    $payload['dataforseo'] = is_string($dataforseo['payload'] ?? null) ? $dataforseo['payload'] : null;
                    $didSync = true;
                } else {
                    $errors[] = 'Brak domeny DataForSEO.';
                }
            }

            if (!$didSync) {
                throw new \RuntimeException(!empty($errors) ? implode(' ', $errors) : 'Brak danych do synchronizacji.');
            }

            $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            SiteSeoMetric::upsertMonthly($siteId, $metricMonth, $metricsToSave, is_string($payloadJson) ? $payloadJson : null);

            Logger::info(
                "SEO sync OK: site_id={$siteId}, semstorm_domain={$semstormDomain}, dataforseo_domain={$dataforseoDomain}, month={$metricMonth}",
                'semstorm'
            );

            return [
                'success' => true,
                'status' => 'saved',
                'message' => 'Zapisano/uzupelniono dane SEO (SEMSTORM + DataForSEO).',
                'metric_month' => $metricMonth,
                'metrics' => $metricsToSave,
            ];
        } catch (\Throwable $e) {
            if (str_contains($e->getMessage(), 'DataForSEO')) {
                Logger::warning(
                    "DataForSEO sync WARN: site_id={$siteId}, semstorm_domain={$semstormDomain}, dataforseo_domain={$dataforseoDomain}, month={$metricMonth}, error={$e->getMessage()}",
                    'semstorm'
                );
            }
            Logger::info(
                "SEO sync FAIL: site_id={$siteId}, semstorm_domain={$semstormDomain}, dataforseo_domain={$dataforseoDomain}, month={$metricMonth}, error={$e->getMessage()}",
                'semstorm'
            );

            return [
                'success' => false,
                'status' => 'error',
                'message' => 'Blad synchronizacji SEO: ' . $e->getMessage(),
                'metric_month' => $metricMonth,
            ];
        }
    }

    private function resolveDomain(array $site): string
    {
        $manual = trim((string) ($site['semstorm_domain'] ?? ''));
        if ($manual !== '') {
            return strtolower($manual);
        }

        $url = trim((string) ($site['url'] ?? ''));
        if ($url === '') {
            return '';
        }

        $host = parse_url($url, PHP_URL_HOST);
        if (!is_string($host) || $host === '') {
            return '';
        }

        return strtolower($host);
    }

    private function resolveDataforseoDomain(array $site, string $fallbackDomain): string
    {
        $manual = trim((string) ($site['dataforseo_domain'] ?? ''));
        if ($manual !== '') {
            return strtolower($manual);
        }

        return $fallbackDomain;
    }

    private function extractPayloadParts($sourcePayload): array
    {
        $default = ['semstorm' => null, 'dataforseo' => null];
        $raw = trim((string) $sourcePayload);
        if ($raw === '') {
            return $default;
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [
                'semstorm' => $raw,
                'dataforseo' => null,
            ];
        }

        return [
            'semstorm' => is_string($decoded['semstorm'] ?? null) ? $decoded['semstorm'] : null,
            'dataforseo' => is_string($decoded['dataforseo'] ?? null) ? $decoded['dataforseo'] : null,
        ];
    }
}
