<?php

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Config;
use Instinctor\Core\DB;


if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * Shipping Service
 * Manages shipping zones, methods, and rate calculation
 */
class ShippingService
{
    // Get all zones
    public function getZones(): array
    {
        $table = DB::prefix() . 'instinctor_shipping_zones';
        return DB::get_results("SELECT * FROM $table ORDER BY sort_order ASC, id ASC");
    }

    public function getZone(int $id): ?object
    {
        $table = DB::prefix() . 'instinctor_shipping_zones';
        return DB::get_row(DB::prepare("SELECT * FROM $table WHERE id = %d", $id));
    }

    public function createZone(array $data): int
    {
        $table = DB::prefix() . 'instinctor_shipping_zones';

        DB::insert($table, [
            'name' => sanitize_text_field($data['name']),
            'countries' => $data['countries'] ?? null,
            'regions' => $data['regions'] ?? null,
            'postcodes' => $data['postcodes'] ?? null,
            'sort_order' => intval($data['sort_order'] ?? 0),
        ]);

        return (int) DB::insert_id();
    }

    public function updateZone(int $id, array $data): bool
    {
        $table = DB::prefix() . 'instinctor_shipping_zones';

        $update = [];
        if (isset($data['name'])) $update['name'] = sanitize_text_field($data['name']);
        if (array_key_exists('countries', $data)) $update['countries'] = $data['countries'];
        if (array_key_exists('regions', $data)) $update['regions'] = $data['regions'];
        if (array_key_exists('postcodes', $data)) $update['postcodes'] = $data['postcodes'];
        if (isset($data['sort_order'])) $update['sort_order'] = intval($data['sort_order']);

        return DB::update($table, $update, ['id' => $id]) !== false;
    }

    public function deleteZone(int $id): bool
    {
        // Delete methods first
        DB::delete(DB::prefix() . 'instinctor_shipping_methods', ['zone_id' => $id]);
        return DB::delete(DB::prefix() . 'instinctor_shipping_zones', ['id' => $id]) !== false;
    }

    // Shipping methods
    public function getMethods(int $zoneId): array
    {
        $table = DB::prefix() . 'instinctor_shipping_methods';
        return DB::get_results(DB::prepare(
            "SELECT * FROM $table WHERE zone_id = %d ORDER BY sort_order ASC, id ASC",
            $zoneId
        ));
    }

    public function getMethod(int $id): ?object
    {
        $table = DB::prefix() . 'instinctor_shipping_methods';
        return DB::get_row(DB::prepare("SELECT * FROM $table WHERE id = %d", $id));
    }

    public function createMethod(array $data): int
    {
        $table = DB::prefix() . 'instinctor_shipping_methods';

        DB::insert($table, [
            'zone_id' => intval($data['zone_id']),
            'name' => sanitize_text_field($data['name']),
            'method_type' => $data['method_type'] ?? 'flat_rate',
            'cost' => floatval($data['cost'] ?? 0),
            'min_amount' => !empty($data['min_amount']) ? floatval($data['min_amount']) : null,
            'enabled' => isset($data['enabled']) ? 1 : 0,
            'sort_order' => intval($data['sort_order'] ?? 0),
            'settings' => isset($data['settings']) ? json_encode($data['settings']) : null,
        ]);

        return (int) DB::insert_id();
    }

    public function updateMethod(int $id, array $data): bool
    {
        $table = DB::prefix() . 'instinctor_shipping_methods';

        $update = [];
        if (isset($data['name'])) $update['name'] = sanitize_text_field($data['name']);
        if (isset($data['method_type'])) $update['method_type'] = $data['method_type'];
        if (isset($data['cost'])) $update['cost'] = floatval($data['cost']);
        if (array_key_exists('min_amount', $data)) {
            $update['min_amount'] = !empty($data['min_amount']) ? floatval($data['min_amount']) : null;
        }
        if (isset($data['enabled'])) $update['enabled'] = $data['enabled'] ? 1 : 0;
        if (isset($data['sort_order'])) $update['sort_order'] = intval($data['sort_order']);
        if (isset($data['settings'])) $update['settings'] = json_encode($data['settings']);

        return DB::update($table, $update, ['id' => $id]) !== false;
    }

    public function deleteMethod(int $id): bool
    {
        return DB::delete(DB::prefix() . 'instinctor_shipping_methods', ['id' => $id]) !== false;
    }

    // Calculate shipping for cart
    public function calculateShipping(array $cart, array $address): array
    {
        $zone = $this->matchZone($address);
        
        if (!$zone) {
            // Return default/fallback shipping
            return [
                'zone' => null,
                'methods' => [],
                'selected' => null,
            ];
        }

        $methods = $this->getMethods($zone->id);
        $availableMethods = [];
        $cartTotal = $cart['totals']['subtotal'] ?? 0;

        foreach ($methods as $method) {
            if (!$method->enabled) continue;

            $available = true;
            $cost = 0;

            switch ($method->method_type) {
                case 'flat_rate':
                    $cost = (float) $method->cost;
                    break;
                    
                case 'shipping':
                    if ($method->min_amount && $cartTotal < $method->min_amount) {
                        $available = false;
                    }
                    $cost = 0;
                    break;
                    
                case 'local_pickup':
                    $cost = (float) $method->cost;
                    break;
            }

            if ($available) {
                $availableMethods[] = [
                    'id' => $method->id,
                    'name' => $method->name,
                    'type' => $method->method_type,
                    'cost' => $cost,
                    'min_amount' => $method->min_amount,
                ];
            }
        }

        // Select cheapest by default
        $selected = null;
        if (!empty($availableMethods)) {
            usort($availableMethods, fn($a, $b) => $a['cost'] <=> $b['cost']);
            $selected = $availableMethods[0];
        }

        return [
            'zone' => [
                'id' => $zone->id,
                'name' => $zone->name,
            ],
            'methods' => $availableMethods,
            'selected' => $selected,
        ];
    }

    // Match address to zone
    public function matchZone(array $address): ?object
    {
        $zones = $this->getZones();
        $country = strtoupper($address['country'] ?? '');
        $state = $address['state'] ?? '';
        $postcode = $address['postcode'] ?? '';

        foreach ($zones as $zone) {
            // Check postcodes first (most specific)
            if ($zone->postcodes) {
                $postcodes = array_map('trim', explode("\n", $zone->postcodes));
                foreach ($postcodes as $pattern) {
                    if ($this->matchPostcode($postcode, $pattern)) {
                        return $zone;
                    }
                }
            }

            // Check regions (country:state)
            if ($zone->regions) {
                $regions = array_map('trim', explode(',', $zone->regions));
                $fullRegion = $country . ':' . $state;
                if (in_array($fullRegion, $regions)) {
                    return $zone;
                }
            }

            // Check countries
            if ($zone->countries) {
                $countries = array_map('trim', explode(',', $zone->countries));
                if (in_array($country, $countries)) {
                    return $zone;
                }
            }
        }

        // Return "Rest of World" zone if exists (zone with no restrictions)
        foreach ($zones as $zone) {
            if (!$zone->countries && !$zone->regions && !$zone->postcodes) {
                return $zone;
            }
        }

        return null;
    }

    private function matchPostcode(string $postcode, string $pattern): bool
    {
        $postcode = strtoupper(preg_replace('/\s+/', '', $postcode));
        $pattern = strtoupper(preg_replace('/\s+/', '', $pattern));

        // Exact match
        if ($postcode === $pattern) {
            return true;
        }

        // Wildcard match (e.g., "90*" matches "90210")
        if (strpos($pattern, '*') !== false) {
            $regex = '/^' . str_replace('*', '.*', preg_quote($pattern, '/')) . '$/';
            return (bool) preg_match($regex, $postcode);
        }

        // Range match (e.g., "90000...90999")
        if (strpos($pattern, '...') !== false) {
            [$start, $end] = explode('...', $pattern);
            return $postcode >= $start && $postcode <= $end;
        }

        return false;
    }

    // Get common countries list
    public static function getCountries(): array
    {
        return [
            'US' => 'United States',
            'CA' => 'Canada',
            'GB' => 'United Kingdom',
            'AU' => 'Australia',
            'DE' => 'Germany',
            'FR' => 'France',
            'ES' => 'Spain',
            'IT' => 'Italy',
            'NL' => 'Netherlands',
            'JP' => 'Japan',
            'CN' => 'China',
            'IN' => 'India',
            'BR' => 'Brazil',
            'MX' => 'Mexico',
            'KR' => 'South Korea',
            'RU' => 'Russia',
            'SE' => 'Sweden',
            'NO' => 'Norway',
            'DK' => 'Denmark',
            'FI' => 'Finland',
            'PL' => 'Poland',
            'AT' => 'Austria',
            'CH' => 'Switzerland',
            'BE' => 'Belgium',
            'IE' => 'Ireland',
            'PT' => 'Portugal',
            'NZ' => 'New Zealand',
            'SG' => 'Singapore',
            'HK' => 'Hong Kong',
        ];
    }
}
