<?php
/**
 * TaxZone — Tax zone data access and address matching.
 *
 * Mirrors ShippingZone's architecture exactly, adapted for tax purposes.
 *
 * Zone matching hierarchy (most specific wins):
 *   postcode match > state match > country match > worldwide (no locations)
 *
 * State codes: "COUNTRY:STATE" (e.g. "US:CA")
 * Postcode patterns: exact, wildcard (902*), or range (90000...90999)
 *
 * @package TitanCart\Modules\Tax
 */

namespace TitanCart\Modules\Tax;

use TitanCart\DB\Database;

defined( 'ABSPATH' ) || exit;

class TaxZone {

    private Database $db;

    public function __construct() {
        $this->db = titancart_db();
    }

    // ══════════════════════════════════════════════════════════════════════════
    // ZONE CRUD
    // ══════════════════════════════════════════════════════════════════════════

    public function get_all(): array {
        $zones = $this->db->getArray(
            "SELECT * FROM {$this->db->table('tax_zones')}
             WHERE status = 'active'
             ORDER BY id ASC"
        );

        foreach ( $zones as &$zone ) {
            $zone['locations'] = $this->get_locations( (int) $zone['id'] );
        }

        return $zones;
    }

    public function get_by_id( int $id ): ?array {
        $zone = $this->db->getRow(
            "SELECT * FROM {$this->db->table('tax_zones')} WHERE id = ?i LIMIT 1",
            $id
        );

        if ( ! $zone ) {
            return null;
        }

        $zone['locations'] = $this->get_locations( $id );
        return $zone;
    }

    public function create( array $data ): int {
        $this->db->query(
            "INSERT INTO {$this->db->table('tax_zones')} (name, status)
             VALUES (?s, ?s)",
            sanitize_text_field( $data['name'] ?? '' ),
            // KI-84: default on BOTH sides — the `in_array` tests the default, so an
            // absent/NULL key reaches the true-branch and must re-apply the `??`.
            in_array( $data['status'] ?? 'active', [ 'active', 'disabled' ], true ) ? ( $data['status'] ?? 'active' ) : 'active'
        );

        return $this->db->lastInsertId();
    }

    public function update( int $id, array $data ): bool {
        $allowed = [ 'name', 'status' ];
        $fields  = array_intersect_key( $data, array_flip( $allowed ) );

        if ( empty( $fields ) ) {
            return false;
        }

        if ( isset( $fields['name'] ) ) {
            $fields['name'] = sanitize_text_field( $fields['name'] );
        }

        $this->db->query(
            "UPDATE {$this->db->table('tax_zones')} SET ?u WHERE id = ?i",
            $fields,
            $id
        );

        return true;
    }

    public function delete( int $id ): bool {
        // Cascade-delete locations and rate rows.
        $this->db->query(
            "DELETE FROM {$this->db->table('tax_zone_locations')} WHERE zone_id = ?i",
            $id
        );
        $this->db->query(
            "DELETE FROM {$this->db->table('tax_rates')} WHERE zone_id = ?i",
            $id
        );
        $this->db->query(
            "DELETE FROM {$this->db->table('tax_zones')} WHERE id = ?i",
            $id
        );

        return true;
    }

    // ══════════════════════════════════════════════════════════════════════════
    // LOCATION CRUD
    // ══════════════════════════════════════════════════════════════════════════

    public function get_locations( int $zone_id ): array {
        return $this->db->getArray(
            "SELECT * FROM {$this->db->table('tax_zone_locations')}
             WHERE zone_id = ?i ORDER BY type ASC, code ASC",
            $zone_id
        );
    }

    public function add_location( int $zone_id, string $type, string $code ): int {
        if ( ! in_array( $type, [ 'country', 'state', 'postcode' ], true ) ) {
            $type = 'country';
        }

        $code = strtoupper( trim( $code ) );

        $this->db->query(
            "INSERT INTO {$this->db->table('tax_zone_locations')} (zone_id, type, code)
             VALUES (?i, ?s, ?s)",
            $zone_id,
            $type,
            $code
        );

        return $this->db->lastInsertId();
    }

    public function remove_location( int $location_id ): bool {
        $this->db->query(
            "DELETE FROM {$this->db->table('tax_zone_locations')} WHERE id = ?i",
            $location_id
        );

        return true;
    }

    // ══════════════════════════════════════════════════════════════════════════
    // ADDRESS MATCHING
    // ══════════════════════════════════════════════════════════════════════════

    /**
     * Return IDs of active tax zones matching the given address,
     * ordered from most-specific to least-specific.
     *
     * @param  array $address  {country, state, postcode}
     * @return int[]
     */
    public function get_matching_zone_ids( array $address ): array {
        $country  = strtoupper( trim( $address['country']  ?? '' ) );
        $state    = strtoupper( trim( $address['state']    ?? '' ) );
        $postcode = strtoupper( trim( $address['postcode'] ?? $address['zip'] ?? '' ) );
        $state_key = ( $country && $state ) ? "{$country}:{$state}" : '';

        $rows = $this->db->getArray(
            "SELECT z.id AS zone_id,
                    l.id AS loc_id,
                    l.type,
                    l.code
             FROM {$this->db->table('tax_zones')} z
             LEFT JOIN {$this->db->table('tax_zone_locations')} l ON l.zone_id = z.id
             WHERE z.status = 'active'
             ORDER BY z.id ASC"
        );

        $zones_map = [];
        foreach ( $rows as $row ) {
            $zid = (int) $row['zone_id'];
            if ( ! isset( $zones_map[ $zid ] ) ) {
                $zones_map[ $zid ] = [ 'locations' => [] ];
            }
            if ( $row['loc_id'] ) {
                $zones_map[ $zid ]['locations'][] = [
                    'type' => $row['type'],
                    'code' => $row['code'],
                ];
            }
        }

        $by_postcode = [];
        $by_state    = [];
        $by_country  = [];
        $worldwide   = [];

        foreach ( $zones_map as $zid => $zone ) {
            $locs = $zone['locations'];

            if ( empty( $locs ) ) {
                $worldwide[] = $zid;
                continue;
            }

            $has_postcode = false;
            $has_state    = false;
            $has_country  = false;

            foreach ( $locs as $loc ) {
                switch ( $loc['type'] ) {
                    case 'country':
                        if ( $loc['code'] === $country ) {
                            $has_country = true;
                        }
                        break;
                    case 'state':
                        if ( $state_key && $loc['code'] === $state_key ) {
                            $has_state = true;
                        }
                        break;
                    case 'postcode':
                        if ( $postcode && $this->postcode_matches( $loc['code'], $postcode ) ) {
                            $has_postcode = true;
                        }
                        break;
                }
            }

            if ( $has_postcode ) {
                $by_postcode[] = $zid;
            } elseif ( $has_state ) {
                $by_state[] = $zid;
            } elseif ( $has_country ) {
                $by_country[] = $zid;
            }
        }

        return array_merge( $by_postcode, $by_state, $by_country, $worldwide );
    }

    // ══════════════════════════════════════════════════════════════════════════
    // INTERNAL HELPERS
    // ══════════════════════════════════════════════════════════════════════════

    private function postcode_matches( string $pattern, string $postcode ): bool {
        $pattern = strtoupper( trim( $pattern ) );

        if ( strpos( $pattern, '...' ) !== false ) {
            [ $from, $to ] = array_map( 'trim', explode( '...', $pattern, 2 ) );
            return strcmp( $postcode, $from ) >= 0 && strcmp( $postcode, $to ) <= 0;
        }

        if ( substr( $pattern, -1 ) === '*' ) {
            $prefix = substr( $pattern, 0, -1 );
            return $prefix === '' || strncmp( $postcode, $prefix, strlen( $prefix ) ) === 0;
        }

        return $postcode === $pattern;
    }
}
