<?php

namespace App\Imports\Blog;

use App\Imports\Blog\Contracts\BlogFileParser;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class CsvBlogParser implements BlogFileParser
{
    /**
     * @param array<string, string> $columnMapping Maps BlogArticleData page-level fields to CSV column headers.
     *   Supported keys: title, slug, url, published_at, status, page_category,
     *   categories, seo_title, seo_description, author, featured_image_url
     * @param array<int, array<string, string>> $contentElementMapping
     *   Maps template_element_id → (field slug → CSV column header).
     * @param string $templateSlug Template slug to use for all imported articles
     */
    public function __construct(
        private readonly array $columnMapping,
        private readonly array $contentElementMapping = [],
        private readonly string $templateSlug = 'blog-article',
    ) {
        //
    }

    /** @return BlogArticleData[] */
    public function parse(string $fileContents): array
    {
        $rows = $this->parseCsv($fileContents);

        if (empty($rows)) {
            return [];
        }

        $articles = [];
        foreach ($rows as $row) {
            $title = trim($this->getMappedValue($row, 'title') ?? '');

            if (empty($title)) {
                continue;
            }

            $slug = $this->getMappedValue($row, 'slug');
            if (!empty($slug) && $this->looksLikeUrl($slug)) {
                $slug = $this->extractSlugFromUrl($slug);
            }
            if (empty($slug)) {
                $url = $this->getMappedValue($row, 'url');
                if (!empty($url)) {
                    $slug = $this->extractSlugFromUrl($url);
                }
            }
            if (empty($slug)) {
                $slug = Str::slug($title);
            }

            $categories = [];
            $categoriesValue = $this->getMappedValue($row, 'categories');
            if (!empty($categoriesValue)) {
                foreach (explode(',', $categoriesValue) as $cat) {
                    $cat = trim($cat);
                    if (!empty($cat)) {
                        $categories[] = [
                            'slug' => Str::slug($cat),
                            'name' => $cat,
                        ];
                    }
                }
            }

            $articles[] = new BlogArticleData(
                title: $title,
                slug: $slug,
                featuredImageUrl: $this->getMappedValue($row, 'featured_image_url'),
                publishedAt: $this->getMappedValue($row, 'published_at'),
                status: $this->getMappedValue($row, 'status') ?? 'published',
                pageCategory: $this->getMappedValue($row, 'page_category') ?? 'blog',
                templateSlug: $this->templateSlug,
                categories: $categories,
                seoTitle: $this->getMappedValue($row, 'seo_title'),
                seoDescription: $this->getMappedValue($row, 'seo_description'),
                authorName: $this->getMappedValue($row, 'author'),
                contentElementValues: $this->extractContentElementValues($row),
            );
        }

        return $articles;
    }

    /**
     * @return array<int, array<string, ?string>>
     */
    private function extractContentElementValues(array $row): array
    {
        $values = [];
        foreach ($this->contentElementMapping as $templateElementId => $fieldColumns) {
            foreach ($fieldColumns as $fieldSlug => $csvColumn) {
                if (!array_key_exists($csvColumn, $row)) {
                    continue;
                }
                $value = $row[$csvColumn];
                $values[(int) $templateElementId][$fieldSlug] = $value !== ''
                    ? $this->sanitiseText($value)
                    : null;
            }
        }
        return $values;
    }

    private function getMappedValue(array $row, string $field): ?string
    {
        $csvColumn = $this->columnMapping[$field] ?? null;

        if ($csvColumn === null || !array_key_exists($csvColumn, $row)) {
            return null;
        }

        $value = $row[$csvColumn];

        return $value !== '' ? $this->sanitiseText($value) : null;
    }

    private function looksLikeUrl(string $value): bool
    {
        return str_contains($value, '/') || preg_match('#^https?://#i', $value) === 1;
    }

    private function extractSlugFromUrl(string $url): ?string
    {
        $path = parse_url($url, PHP_URL_PATH);
        if ($path === null || $path === false) {
            $path = $url;
        }

        $segments = array_values(array_filter(
            explode('/', $path),
            fn($segment) => $segment !== ''
        ));

        if (empty($segments)) {
            return null;
        }

        return end($segments);
    }

    private function sanitiseText(?string $text): ?string
    {
        if ($text === null) {
            return null;
        }

        // Reverse double-encoded UTF-8 (UTF-8 misread as Windows-1252, then re-encoded)
        $decoded = @iconv('UTF-8', 'Windows-1252//IGNORE', $text);

        if ($decoded !== false && mb_check_encoding($decoded, 'UTF-8')) {
            return $decoded;
        }

        return $text;
    }

    /**
     * @return array<int, array<string, string>>
     */
    private function parseCsv(string $fileContents): array
    {
        $tempPath = 'blog-import/temp-' . uniqid('', true) . '.csv';
        Storage::disk('local')->put($tempPath, $fileContents);

        try {
            $handle = fopen(Storage::disk('local')->path($tempPath), 'r');
            if ($handle === false) {
                return [];
            }

            $headers = fgetcsv($handle);
            if ($headers === false) {
                fclose($handle);
                return [];
            }

            $headers = array_map('trim', $headers);

            $rows = [];
            while (($values = fgetcsv($handle)) !== false) {
                if (count($values) !== count($headers)) {
                    continue;
                }

                $rows[] = array_combine($headers, $values);
            }

            fclose($handle);

            return $rows;
        } finally {
            Storage::disk('local')->delete($tempPath);
        }
    }
}
