<?php

namespace Kirki\App\Services;

defined('ABSPATH') || exit;

use Exception;
use Kirki\App\DTO\GoogleFontDTO;
use Kirki\App\Supports\Facades\GlobalData;
use Kirki\App\Supports\FileHandler;
use Kirki\Framework\Http\Response;
use Kirki\Framework\Supports\Facades\File;
use Kirki\Framework\Supports\Facades\Http;

use function Kirki\App\get_upload_directory;
use function Kirki\App\get_upload_directory_url;
use function Kirki\Framework\clean_path;

class FontService
{
    public function download_google_font(GoogleFontDTO $payload)
    {
		// Extra security: keep only a-z, 0-9, and hyphens
		$font_family_slug = preg_replace('/[^a-z0-9\-]/i', '', sanitize_title_with_dashes(basename($payload->family)));

        if (empty($font_family_slug) || strpos($font_family_slug, '..') !== false) {
            throw new Exception(esc_html__('Not valid font family.', 'kirki'), Response::FORBIDDEN);
        }

        $has_write_permission = File::is_writable(get_upload_directory());

        if (!$has_write_permission) {
            throw new Exception(esc_html__('Upload directory is not writable.', 'kirki'), Response::FORBIDDEN);
        }

        $font_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false);
        $css_file_path = clean_path($font_dir . "/{$font_family_slug}.css", false);

        FileHandler::verify_directory_traversal($css_file_path);

        if (File::exists($css_file_path)) {
            throw new Exception(esc_html__('Font already downloaded.', 'kirki'), Response::FORBIDDEN);
        }

        $font_url_parts = wp_parse_url($payload->fontUrl);

        if (!$font_url_parts) {
            throw new Exception(esc_html__('Invalid URL.', 'kirki'));
        }

        if (($font_url_parts['scheme'] ?? '') !== 'https') {
            throw new Exception(esc_html__('Only HTTPS is allowed.', 'kirki'));
        }

        if (($font_url_parts['host'] ?? '') !== 'fonts.googleapis.com') {
            throw new Exception(esc_html__('Only Google Fonts is supported.', 'kirki'));
        }

        try {
            if (!File::is_directory($font_dir)) {
				File::make_dir($font_dir);
			}

            $response = Http::with_options([
                'redirection' => 0
            ])->get($payload->fontUrl);

            if ($response->failed()) {
                throw new Exception(esc_html__('Failed to fetch Google Fonts CSS.', 'kirki'));
            }

            $formats = [
                'woff2' => 'woff2',
				'woff'  => 'woff',
				'ttf'   => 'truetype',
				'otf'   => 'opentype',
            ];

            // Keep Google's CSS verbatim (all weights, styles and unicode-range)
            // and only swap the remote font files for their local counterparts.
            $css = $response->body();

            if (!preg_match_all('/url\(\s*["\']?([^"\'()]+)["\']?\s*\)/i', $css, $matches)) {
                throw new Exception(esc_html__('No usable font files were downloaded.', 'kirki'));
            }

            $local_files = [];

            foreach (array_unique($matches[1]) as $url) {
                $url_parts = wp_parse_url($url);

                if (!$url_parts) {
                    throw new Exception(esc_html__('Invalid URL.', 'kirki'));
                }

                if (($url_parts['scheme'] ?? '') !== 'https') {
                    throw new Exception(esc_html__('Only HTTPS is allowed.', 'kirki'));
                }

                if (($url_parts['host'] ?? '') !== 'fonts.gstatic.com') {
                    throw new Exception(esc_html__('Only Google Fonts is supported.', 'kirki'));
                }

                $extension = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));

                if (!array_key_exists($extension, $formats)) {
                    /* translators: %s: file extension */
                    throw new Exception(sprintf(esc_html__('Invalid or unsafe file extension: %s.', 'kirki'), $extension));
                }

                $file_name = preg_replace('/[^a-zA-Z0-9._-]/', '', basename(parse_url($url, PHP_URL_PATH)));

                if (empty($file_name)) {
                    throw new Exception(esc_html__('Invalid file name.', 'kirki'));
                }

                $file_path = clean_path($font_dir . '/' . $file_name, false);

                FileHandler::verify_directory_traversal($file_path);

                if (!File::exists($file_path)) {
                    $font_response = Http::with_options([
                        'redirection' => 0
                    ])->get($url);

                    if ($font_response->failed()) {
                        throw new Exception(esc_html__('Failed to fetch font file.', 'kirki'));
                    }

                    File::put($file_path, $font_response->__toString());
                }

                $local_files[$url] = $file_name;
            }

            $local_css = str_replace(array_keys($local_files), array_values($local_files), $css);

            if (empty($local_css)) {
                throw new Exception(esc_html__('No usable font files were downloaded.', 'kirki'));
            }

            File::put($css_file_path, $local_css);
			$payload->localUrl = clean_path(get_upload_directory_url() . "/kirki-fonts/{$font_family_slug}/{$font_family_slug}.css", false);

            $this->save_google_font_into_global_custom_fonts($payload);

            return $payload;

        } catch (Exception $exception) {
            if (File::is_directory($font_dir)) {
				File::delete($font_dir);
			}

            throw $exception;
        }
    }

    public function remove_google_font(GoogleFontDTO $payload) {
        $font_family_slug = sanitize_title_with_dashes(basename($payload->family));
		$font_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false);

        FileHandler::verify_directory_traversal($font_dir);

        if (File::is_directory($font_dir)) {
            File::delete($font_dir);
        }

        $payload->exclude(['localUrl']);

        $this->save_google_font_into_global_custom_fonts($payload);

        return true;
    }

    protected function save_google_font_into_global_custom_fonts(GoogleFontDTO $font) {
		// first get the font data
		$custom_fonts = GlobalData::get_global_custom_fonts();

		if (empty($custom_fonts)) {
			$custom_fonts = [];
		}

		if (!empty($font->family)) {
			$custom_fonts[$font->family] = $font->to_array();

            GlobalData::update_global_custom_fonts($custom_fonts);
		}
	}

    public function remove_custom_fonts_permanently_from_directory(array $fonts)
    {
        foreach ($fonts as $font) {
			// Remove font from local.
			$font_family_slug = sanitize_title_with_dashes(basename($font['family']));
            $font_local_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false);

            FileHandler::verify_directory_traversal($font_local_dir);

            if (File::is_directory($font_local_dir)) {
                File::delete($font_local_dir);
            }
		}
    }
}