<?php
namespace Crawl\Services;

use Crawl\Repositories\CrawlImageRepository;
use Crawl\Repositories\CrawlLinkRepository;
use Crawl\Repositories\CrawlPageRepository;
use Crawl\Services\CrawlComparisonService;
use Crawl\Services\SchemaValidatorService;
use Crawl\Services\SiteHealthService;

class AuditService
{
    private CrawlPageRepository $pages;
    private CrawlLinkRepository $links;
    private CrawlImageRepository $images;
    private IssueService $issues;

    public function __construct()
    {
        $this->pages = new CrawlPageRepository();
        $this->links = new CrawlLinkRepository();
        $this->images = new CrawlImageRepository();
        $this->issues = new IssueService();
    }

    public function websiteAudit(int $projectId, int $runId, array $filters, int $limit, int $offset, string $sortBy, string $sortDir): array
    {
        $set        = $this->pages->paginated($runId, $filters, $limit, $offset, $sortBy, $sortDir);
        $allPages   = $this->pages->allByRun($runId);
        $allImages  = $this->images->allByRun($runId);
        $pageSummary  = $this->issues->summarize($allPages);
        $imageSummary = $this->issues->summarizeImages($allImages, $allPages);

        return [
            'project_id'    => $projectId,
            'crawl_run_id'  => $runId,
            'crawl_columns' => ['url', 'status_code', 'title', 'title_length', 'meta_description_length', 'h1', 'word_count', 'depth', 'content_type', 'response_time'],
            'crawl_rows'    => $set['rows'],
            'crawl_total'   => $set['total'],
            'issue_summary' => array_merge($pageSummary, $imageSummary),
        ];
    }

    public function technicalIssues(int $runId): array
    {
        return $this->issues->summarize($this->pages->allByRun($runId));
    }

    public function technicalDetail(int $runId, int $limit, int $offset): array
    {
        $set  = $this->pages->technicalPages($runId, $limit, $offset);
        $rows = array_map(function (array $row): array {
            $code      = (int)($row['status_code'] ?? 0);
            $isNoindex = stripos($row['meta_robots'] ?? '', 'noindex') !== false
                      || stripos($row['x_robots_tag'] ?? '', 'noindex') !== false;

            if ($code >= 500)                             $issueType = 'Server Error';
            elseif ($code >= 400)                         $issueType = 'Client Error';
            elseif ($code >= 300)                         $issueType = 'Redirect';
            elseif ((float)($row['response_time'] ?? 0) > 3.0) $issueType = 'Slow Page';
            elseif ($isNoindex)                           $issueType = 'Noindex';
            else                                          $issueType = 'Check';

            return [
                'url'          => $row['url'] ?? '',
                'status_code'  => $row['status_code'] ?? '',
                'issue_type'   => $issueType,
                'canonical_url'=> $row['canonical_url'] ?? '',
                'indexability' => $isNoindex ? 'Noindex' : 'Indexable',
                'response_time'=> number_format((float)($row['response_time'] ?? 0), 3) . 's',
            ];
        }, $set['rows']);

        return ['total' => $set['total'], 'rows' => $rows];
    }

    public function websiteLevelSummary(int $runId): array
    {
        return [
            'status_distribution' => $this->pages->statusDistribution($runId),
            'depth_distribution' => $this->pages->depthDistribution($runId),
            'content_type_distribution' => $this->pages->contentTypeDistribution($runId),
            'orphan_sample' => $this->links->orphanSample($runId),
        ];
    }

    public function pageDetail(int $runId, string $url): ?array
    {
        $page = $this->pages->getByRunAndUrl($runId, $url);
        if (!$page) {
            return null;
        }

        return [
            'page' => $page,
            'inlinks' => $this->links->inlinksForUrl($runId, $url),
            'outlinks' => $this->links->outlinksForUrl($runId, $url),
            'images' => $this->images->byPage($runId, $url),
        ];
    }

    public function fullAudit(int $projectId, int $runId, array $filters, int $limit): array
    {
        $set = $this->pages->fullAuditRows($runId, $filters, $limit);
        return [
            'project_id'  => $projectId,
            'crawl_run_id'=> $runId,
            'total'       => $set['total'],
            'limit'       => $limit,
            'rows'        => $set['rows'],
        ];
    }

    public function healthScore(int $runId): array
    {
        $pages  = $this->pages->allByRun($runId);
        $issues = $this->issues->summarize($pages);
        $score  = $this->issues->healthScore($issues, count($pages));
        return ['score' => $score, 'total_pages' => count($pages), 'issue_count' => count($issues)];
    }

    public function duplicateGroups(int $runId): array
    {
        $rows = $this->pages->duplicateGroups($runId);
        return array_map(function (array $row): array {
            $urls = explode('|||', $row['urls']);
            return ['hash' => $row['content_hash'], 'count' => (int)$row['cnt'], 'urls' => $urls];
        }, $rows);
    }

    public function canonicalAudit(int $runId): array
    {
        return $this->pages->canonicalIssues($runId);
    }

    public function headingAudit(int $runId, int $limit, int $offset): array
    {
        $rows = $this->pages->headingAudit($runId, $limit, $offset);
        return array_map(function (array $row): array {
            $h2s    = json_decode($row['h2_list'] ?? '[]', true) ?: [];
            $h3s    = json_decode($row['h3_list'] ?? '[]', true) ?: [];
            $issues = [];
            if (empty($row['h1']))   $issues[] = 'Missing H1';
            if (count($h2s) === 0)   $issues[] = 'No H2s';
            if (count($h2s) > 10)    $issues[] = 'Many H2s (' . count($h2s) . ')';
            return [
                'url'       => $row['url'],
                'h1'        => $row['h1'] ?? '',
                'h2_count'  => count($h2s),
                'h3_count'  => count($h3s),
                'h2_sample' => array_slice($h2s, 0, 3),
                'issues'    => implode(', ', $issues) ?: 'OK',
            ];
        }, $rows);
    }

    public function linkEquity(int $runId, int $limit, int $offset): array
    {
        $rows       = $this->links->linkEquityMap($runId, $limit, $offset);
        $orphans    = $this->links->orphanCount($runId);
        $anchors    = $this->links->anchorTextDistribution($runId);
        return [
            'pages'   => array_map(function (array $r): array {
                $anchors = array_filter(explode('|||', $r['anchors'] ?? ''));
                return [
                    'url'          => $r['url'],
                    'inlink_count' => (int)$r['inlink_count'],
                    'top_anchors'  => array_slice(array_unique($anchors), 0, 3),
                ];
            }, $rows),
            'orphan_count'    => $orphans,
            'anchor_dist'     => $anchors,
        ];
    }

    public function crawlBudget(int $runId): array
    {
        return $this->pages->crawlBudgetStats($runId);
    }

    public function siteHealth(string $domain, int $runId): array
    {
        $pages    = $this->pages->allByRun($runId);
        $service  = new SiteHealthService();
        $result   = $service->analyse($domain, $pages);

        $result['schema_pages_count'] = count(array_filter($pages, fn($p) =>
            !empty($p['json_ld_schema']) &&
            !in_array($p['json_ld_schema'], ['', '[]', 'null'], true)
        ));
        $result['total_pages']   = count($pages);
        $result['indexed_pages'] = count(array_filter($pages, fn($p) => (int)($p['status_code'] ?? 0) === 200));

        return $result;
    }

    public function crawlComparison(int $runIdA, int $runIdB): array
    {
        return (new CrawlComparisonService())->compare($runIdA, $runIdB);
    }

    public function schemaValidation(int $runId): array
    {
        $pages = $this->pages->allByRun($runId);
        return (new SchemaValidatorService())->validateRun($pages);
    }

    public function tabData(int $runId, string $tab, int $limit, int $offset): array
    {
        switch ($tab) {
            case 'all_urls':
                return $this->pages->paginated($runId, [], $limit, $offset, 'url', 'ASC')['rows'];
            case 'meta_tags':
                return $this->pages->paginated($runId, [], $limit, $offset, 'url', 'ASC')['rows'];
            case 'url_issues':
                return $this->pages->paginated($runId, [], $limit, $offset, 'url', 'ASC')['rows'];
            case 'schema_markup':
                // All pages — JS will extract + display schema type from json_ld_schema
                return array_map(function(array $r): array {
                    return [
                        'url'            => $r['url'],
                        'status_code'    => $r['status_code'],
                        'depth'          => $r['depth'],
                        'json_ld_schema' => $r['json_ld_schema'] ?? '[]',
                    ];
                }, $this->pages->paginated($runId, [], $limit, $offset, 'url', 'ASC')['rows']);
            case 'content':
                return $this->pages->paginated($runId, [], $limit, $offset, 'word_count', 'DESC')['rows'];
            case 'images':
                return $this->images->tabData($runId, $limit, $offset);
            case 'inlinks':
            case 'outlinks':
            case 'external_links':
                return $this->links->tabData($runId, $tab, $limit, $offset);

            // ── Technical audit sub-tabs ────────────────────────────────
            case 'tech_security':
                $rows = $this->pages->paginated($runId, [], $limit, $offset, 'status_code', 'DESC')['rows'];
                return array_map(fn(array $r): array => [
                    'url'               => $r['url'],
                    'content_type'      => $r['content_type'] ?? '',
                    'status_code'       => $r['status_code'],
                    'indexability'      => $this->indexabilityStatus($r)['status'],
                    'indexability_status'=> $this->indexabilityStatus($r)['reason'],
                    'inlinks'           => 0, // enriched client-side from rawRows
                    'response_time'     => $r['response_time'],
                    'canonical_url'     => $r['canonical_url'] ?? '',
                    'meta_robots'       => $r['meta_robots'] ?? '',
                    'x_robots_tag'      => $r['x_robots_tag'] ?? '',
                    'redirect_url'      => $r['redirect_url'] ?? '',
                    'redirect_type'     => $r['redirect_type'] ?? '',
                ], $rows);

            case 'tech_canonical':
                $rows = $this->pages->canonicalIssues($runId);
                return array_map(fn(array $r): array => [
                    'url'                => $r['url'],
                    'indexability'       => $this->indexabilityStatus($r)['status'],
                    'indexability_status'=> $this->indexabilityStatus($r)['reason'],
                    'canonical_url'      => $r['canonical_url'] ?? '',
                    'meta_robots'        => $r['meta_robots'] ?? '',
                    'x_robots_tag'       => $r['x_robots_tag'] ?? '',
                ], $rows);

            case 'tech_schema':
                $pages  = $this->pages->allByRun($runId);
                $result = (new \Crawl\Services\SchemaValidatorService())->validateRun($pages);
                return $result['results'] ?? [];

            default:
                return [];
        }
    }

    private function indexabilityStatus(array $page): array
    {
        $code    = (int)($page['status_code'] ?? 0);
        $robots  = strtolower((string)($page['meta_robots'] ?? '')) . ' ' . strtolower((string)($page['x_robots_tag'] ?? ''));
        $noindex = str_contains($robots, 'noindex');
        if ($noindex)   return ['status' => 'Not Indexable', 'reason' => 'Noindex tag'];
        if ($code >= 400) return ['status' => 'Not Indexable', 'reason' => "Error ({$code})"];
        if ($code >= 300) return ['status' => 'Not Indexable', 'reason' => "Redirect ({$code})"];
        if ($code !== 200) return ['status' => 'Not Indexable', 'reason' => "Non-200 ({$code})"];
        return ['status' => 'Indexable', 'reason' => 'Indexable'];
    }
}
