<?php
/**
 * arag-activity-cost.php — بهای تمام‌شده فعالیت‌های فروشگاه + گراف سهم
 * سفارش ↔ فعالیت (COGS / بسته‌بندی / ارسال / بازاریابی) ↔ نفر/کانال
 */
if ( ! defined( 'ABSPATH' ) ) exit;

class Arag_Activity_Cost {

    public static function default_activities(): array {
        return [
            [ 'code' => 'cogs',        'name' => 'بهای تمام‌شده کالا',     'pool' => 'cogs',        'weight' => 100, 'roles' => [ 'company' ],           'driver' => 'fixed',  'sort' => 10 ],
            [ 'code' => 'pick_pack',   'name' => 'چیدن و بسته‌بندی',       'pool' => 'fulfillment', 'weight' => 45,  'roles' => [ 'picker', 'packer' ], 'driver' => 'hours',  'sort' => 20 ],
            [ 'code' => 'quality',     'name' => 'کنترل کیفیت خروجی',      'pool' => 'fulfillment', 'weight' => 20,  'roles' => [ 'packer', 'qa' ],     'driver' => 'weight', 'sort' => 30 ],
            [ 'code' => 'shipping',    'name' => 'آمادهسازی و ارسال',      'pool' => 'shipping',    'weight' => 100, 'roles' => [ 'shipper', 'company' ], 'driver' => 'weight', 'sort' => 40 ],
            [ 'code' => 'marketing',   'name' => 'بازاریابی / معرف',       'pool' => 'marketing',   'weight' => 100, 'roles' => [ 'marketer' ],         'driver' => 'weight', 'sort' => 50 ],
            [ 'code' => 'payment_fee', 'name' => 'کارمزد درگاه / عملیات',  'pool' => 'ops',         'weight' => 100, 'roles' => [ 'company' ],           'driver' => 'fixed',  'sort' => 60 ],
        ];
    }

    public static function role_labels(): array {
        return [
            'picker'   => 'چینش انبار',
            'packer'   => 'بسته‌بندی',
            'qa'       => 'کنترل کیفیت',
            'shipper'  => 'ارسال',
            'marketer' => 'بازاریاب / معرف',
            'company'  => 'شرکت / سربار',
        ];
    }

    public static function ensure_tables(): void {
        global $wpdb;
        $pre = $wpdb->prefix;
        $charset = 'DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';

        $wpdb->query( "CREATE TABLE IF NOT EXISTS {$pre}arag_acc_activities (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            code VARCHAR(40) NOT NULL,
            name VARCHAR(160) NOT NULL,
            pool VARCHAR(30) NOT NULL DEFAULT 'fulfillment',
            weight DECIMAL(8,2) NOT NULL DEFAULT 0,
            roles_json JSON NULL,
            driver VARCHAR(20) NOT NULL DEFAULT 'weight',
            sort_order INT NOT NULL DEFAULT 0,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id), UNIQUE KEY code (code)
        ) {$charset}" );

        $wpdb->query( "CREATE TABLE IF NOT EXISTS {$pre}arag_acc_order_crew (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            order_id BIGINT UNSIGNED NOT NULL,
            user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            role_code VARCHAR(30) NOT NULL DEFAULT 'packer',
            weight DECIMAL(8,2) NOT NULL DEFAULT 1,
            hours DECIMAL(8,2) NOT NULL DEFAULT 0,
            channel VARCHAR(60) NOT NULL DEFAULT '',
            note VARCHAR(255) NOT NULL DEFAULT '',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id), KEY order_id (order_id), KEY user_id (user_id)
        ) {$charset}" );

        $wpdb->query( "CREATE TABLE IF NOT EXISTS {$pre}arag_acc_activity_costs (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            order_id BIGINT UNSIGNED NOT NULL,
            activity_code VARCHAR(40) NOT NULL,
            activity_name VARCHAR(160) NOT NULL DEFAULT '',
            pool VARCHAR(30) NOT NULL DEFAULT '',
            person_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            person_label VARCHAR(200) NOT NULL DEFAULT '',
            role_code VARCHAR(30) NOT NULL DEFAULT '',
            driver_qty DECIMAL(12,4) NOT NULL DEFAULT 0,
            share_pct DECIMAL(8,4) NOT NULL DEFAULT 0,
            amount DECIMAL(15,2) NOT NULL DEFAULT 0,
            revenue_share DECIMAL(15,2) NOT NULL DEFAULT 0,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id), KEY order_id (order_id), KEY person (person_id), KEY activity (activity_code)
        ) {$charset}" );

        $n = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$pre}arag_acc_activities" );
        if ( $n === 0 ) {
            foreach ( self::default_activities() as $a ) {
                $wpdb->insert( "{$pre}arag_acc_activities", [
                    'code' => $a['code'], 'name' => $a['name'], 'pool' => $a['pool'],
                    'weight' => $a['weight'], 'roles_json' => wp_json_encode( $a['roles'] ),
                    'driver' => $a['driver'], 'sort_order' => $a['sort'], 'is_active' => 1,
                ] );
            }
        }
    }

    public static function list_activities( bool $active_only = true ): array {
        global $wpdb;
        self::ensure_tables();
        $t = $wpdb->prefix . 'arag_acc_activities';
        $sql = "SELECT * FROM {$t}" . ( $active_only ? ' WHERE is_active=1' : '' ) . ' ORDER BY sort_order, id';
        $rows = $wpdb->get_results( $sql, ARRAY_A ) ?: [];
        return array_map( static function( $r ) {
            return [
                'id' => (int) $r['id'], 'code' => $r['code'], 'name' => $r['name'],
                'pool' => $r['pool'], 'weight' => (float) $r['weight'],
                'roles' => json_decode( $r['roles_json'] ?? '[]', true ) ?: [],
                'driver' => $r['driver'], 'sort' => (int) $r['sort_order'],
                'is_active' => (int) $r['is_active'] === 1,
            ];
        }, $rows );
    }

    public static function get_crew( int $order_id ): array {
        global $wpdb;
        self::ensure_tables();
        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT c.*, u.display_name FROM {$wpdb->prefix}arag_acc_order_crew c
             LEFT JOIN {$wpdb->users} u ON u.ID = c.user_id
             WHERE c.order_id = %d ORDER BY c.id", $order_id
        ), ARRAY_A ) ?: [];
        if ( $rows ) {
            return array_map( static function( $r ) {
                return [
                    'id' => (int) $r['id'], 'order_id' => (int) $r['order_id'],
                    'user_id' => (int) $r['user_id'],
                    'name' => $r['display_name'] ?: ( $r['note'] ?: ( $r['user_id'] ? 'کاربر #' . $r['user_id'] : 'شرکت' ) ),
                    'role' => $r['role_code'], 'weight' => (float) $r['weight'],
                    'hours' => (float) $r['hours'], 'channel' => $r['channel'] ?? '', 'note' => $r['note'] ?? '',
                ];
            }, $rows );
        }
        return self::_infer_crew( $order_id );
    }

    private static function _infer_crew( int $order_id ): array {
        global $wpdb;
        $order = $wpdb->get_row( $wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}arag_orders WHERE id = %d", $order_id
        ), ARRAY_A );
        if ( ! $order ) return [];

        $crew = [
            [ 'user_id' => 0, 'name' => 'شرکت', 'role' => 'company', 'weight' => 1, 'hours' => 0, 'channel' => 'shop', 'note' => 'خودکار', 'inferred' => true ],
            [ 'user_id' => 0, 'name' => 'بسته‌بندی فروشگاه', 'role' => 'packer', 'weight' => 1, 'hours' => 0.5, 'channel' => '', 'note' => 'خودکار', 'inferred' => true ],
            [ 'user_id' => 0, 'name' => 'ارسال ' . ( $order['shipping_method'] ?? 'post' ), 'role' => 'shipper', 'weight' => 1, 'hours' => 0.3, 'channel' => $order['shipping_method'] ?? '', 'note' => 'خودکار', 'inferred' => true ],
        ];

        /* معرف از کد تخفیف REF* */
        $coupon = strtoupper( (string) ( $order['coupon_code'] ?? '' ) );
        if ( str_starts_with( $coupon, 'REF' ) && file_exists( ACC_PATH . 'includes/helpers/arag-referral.php' ) ) {
            require_once ACC_PATH . 'includes/helpers/arag-referral.php';
            if ( class_exists( 'Arag_Referral' ) && method_exists( 'Arag_Referral', 'owner_of_code' ) ) {
                $uid = (int) Arag_Referral::owner_of_code( $coupon );
                if ( $uid ) {
                    $name = (string) $wpdb->get_var( $wpdb->prepare(
                        "SELECT display_name FROM {$wpdb->users} WHERE ID=%d", $uid
                    ) );
                    $crew[] = [
                        'user_id' => $uid, 'name' => $name ?: "معرف #{$uid}",
                        'role' => 'marketer', 'weight' => 1, 'hours' => 0,
                        'channel' => 'referral', 'note' => $coupon, 'inferred' => true,
                    ];
                }
            }
        }
        return $crew;
    }

    public static function save_crew( int $order_id, array $members ): array {
        global $wpdb;
        self::ensure_tables();
        $t = $wpdb->prefix . 'arag_acc_order_crew';
        $wpdb->delete( $t, [ 'order_id' => $order_id ] );
        $n = 0;
        foreach ( $members as $m ) {
            $wpdb->insert( $t, [
                'order_id'   => $order_id,
                'user_id'    => (int) ( $m['user_id'] ?? 0 ),
                'role_code'  => sanitize_key( (string) ( $m['role'] ?? 'packer' ) ),
                'weight'     => max( 0.01, (float) ( $m['weight'] ?? 1 ) ),
                'hours'      => max( 0, (float) ( $m['hours'] ?? 0 ) ),
                'channel'    => sanitize_text_field( (string) ( $m['channel'] ?? '' ) ),
                'note'       => sanitize_text_field( (string) ( $m['note'] ?? '' ) ),
            ] );
            $n++;
        }
        return [ 'success' => true, 'saved' => $n, 'data' => self::get_crew( $order_id ) ];
    }

    /** استخرهای هزینه از مبلغ سفارش + درصدهای ERP */
    public static function pools_for_order( array $order ): array {
        if ( ! class_exists( 'Arag_Erp' ) ) {
            require_once ACC_PATH . 'includes/helpers/arag-erp.php';
        }
        $cfg = Arag_Erp::get_config();
        $total = (float) ( $order['total'] ?? $order['subtotal'] ?? 0 );
        $cogs_pct = (float) ( $cfg['cogs_pct'] ?? 55 );
        $fulfill_pct = (float) ( $cfg['fulfillment_pct'] ?? 4 );
        $ship_pct = (float) ( $cfg['shipping_cost_pct'] ?? 3 );
        $mkt_pct = (float) ( $cfg['marketing_pct'] ?? 2 );
        $ops_pct = (float) ( $cfg['ops_pct'] ?? 1.5 );

        return [
            'cogs'        => round( $total * $cogs_pct / 100, 2 ),
            'fulfillment' => round( $total * $fulfill_pct / 100, 2 ),
            'shipping'    => round( $total * $ship_pct / 100, 2 ),
            'marketing'   => round( $total * $mkt_pct / 100, 2 ),
            'ops'         => round( $total * $ops_pct / 100, 2 ),
            'revenue'     => $total,
        ];
    }

    public static function allocate( array $order ): array {
        $order_id = (int) ( $order['id'] ?? 0 );
        $pools = self::pools_for_order( $order );
        $crew = self::get_crew( $order_id );
        $activities = self::list_activities( true );
        $lines = [];
        $person_totals = [];

        foreach ( $activities as $act ) {
            $pool_amt = (float) ( $pools[ $act['pool'] ] ?? 0 );
            if ( $pool_amt <= 0 ) continue;

            $pool_w = 0.0;
            foreach ( $activities as $a2 ) {
                if ( $a2['pool'] === $act['pool'] ) $pool_w += (float) $a2['weight'];
            }
            if ( $pool_w <= 0 ) continue;
            $activity_amount = round( $pool_amt * ( (float) $act['weight'] / $pool_w ), 2 );
            if ( $activity_amount <= 0 ) continue;

            $eligible = array_values( array_filter( $crew, static function( $m ) use ( $act ) {
                return in_array( $m['role'], $act['roles'], true );
            } ) );

            if ( empty( $eligible ) && in_array( 'company', $act['roles'], true ) ) {
                $lines[] = self::_line( $act, 0, 'شرکت', 'company', 1, 100, $activity_amount );
                continue;
            }
            if ( empty( $eligible ) ) {
                $lines[] = self::_line( $act, 0, 'شرکت / تخصیص‌نشده', 'company', 1, 100, $activity_amount );
                continue;
            }

            $den = 0.0;
            foreach ( $eligible as $m ) {
                $den += $act['driver'] === 'hours'
                    ? max( 0.01, (float) $m['hours'] )
                    : max( 0.01, (float) $m['weight'] );
            }
            $allocated = 0.0;
            $last = count( $eligible ) - 1;
            foreach ( $eligible as $i => $m ) {
                $qty = $act['driver'] === 'hours'
                    ? max( 0.01, (float) $m['hours'] )
                    : max( 0.01, (float) $m['weight'] );
                $pct = round( 100 * $qty / $den, 4 );
                $amt = ( $i === $last )
                    ? round( $activity_amount - $allocated, 2 )
                    : round( $activity_amount * $qty / $den, 2 );
                $allocated += $amt;
                $pid = (int) ( $m['user_id'] ?? 0 );
                $label = $m['name'] ?? ( $pid ? "کاربر #{$pid}" : 'شرکت' );
                $lines[] = self::_line( $act, $pid, $label, $m['role'] ?? '', $qty, $pct, $amt );

                $key = $pid > 0 ? (string) $pid : ( 'role:' . ( $m['role'] ?? 'company' ) );
                if ( ! isset( $person_totals[ $key ] ) ) {
                    $person_totals[ $key ] = [
                        'person_id' => $pid,
                        'name' => $label,
                        'role' => $m['role'] ?? '',
                        'amount' => 0,
                        'by_activity' => [],
                    ];
                }
                $person_totals[ $key ]['amount'] += $amt;
                $person_totals[ $key ]['by_activity'][ $act['code'] ] =
                    ( $person_totals[ $key ]['by_activity'][ $act['code'] ] ?? 0 ) + $amt;
            }
        }

        $rev = (float) ( $pools['revenue'] ?? 0 );
        $psum = array_sum( array_column( $person_totals, 'amount' ) );
        if ( $rev > 0 && $psum > 0 ) {
            foreach ( $person_totals as &$pt ) {
                $pt['revenue_share'] = round( $rev * ( $pt['amount'] / $psum ), 2 );
            }
            unset( $pt );
        }

        return [
            'order_id' => $order_id,
            'order_number' => $order['order_number'] ?? '',
            'pools' => $pools,
            'crew' => $crew,
            'activities' => $activities,
            'lines' => $lines,
            'person_shares' => array_values( $person_totals ),
            'totals' => [
                'activity_cost' => array_sum( array_column( $lines, 'amount' ) ),
                'revenue' => $rev,
                'margin' => max( 0, $rev - array_sum( array_column( $lines, 'amount' ) ) ),
            ],
        ];
    }

    private static function _line( array $act, int $pid, string $label, string $role, float $qty, float $pct, float $amt ): array {
        return [
            'activity_code' => $act['code'],
            'activity_name' => $act['name'],
            'pool' => $act['pool'],
            'person_id' => $pid,
            'person_label' => $label,
            'role_code' => $role,
            'driver_qty' => $qty,
            'share_pct' => $pct,
            'amount' => $amt,
            'revenue_share' => 0,
        ];
    }

    public static function persist( int $order_id, array $allocation ): void {
        global $wpdb;
        self::ensure_tables();
        $t = $wpdb->prefix . 'arag_acc_activity_costs';
        $wpdb->delete( $t, [ 'order_id' => $order_id ] );
        foreach ( $allocation['lines'] ?? [] as $ln ) {
            $wpdb->insert( $t, [
                'order_id' => $order_id,
                'activity_code' => $ln['activity_code'],
                'activity_name' => $ln['activity_name'],
                'pool' => $ln['pool'],
                'person_id' => (int) $ln['person_id'],
                'person_label' => $ln['person_label'],
                'role_code' => $ln['role_code'],
                'driver_qty' => $ln['driver_qty'],
                'share_pct' => $ln['share_pct'],
                'amount' => $ln['amount'],
                'revenue_share' => $ln['revenue_share'] ?? 0,
            ] );
        }
        /* ذخیره در سفارش */
        $col = $wpdb->get_var( "SHOW COLUMNS FROM {$wpdb->prefix}arag_orders LIKE 'acc_splits_json'" );
        if ( ! $col ) {
            $wpdb->query( "ALTER TABLE {$wpdb->prefix}arag_orders ADD COLUMN acc_splits_json JSON NULL" );
        }
        $wpdb->update( "{$wpdb->prefix}arag_orders", [
            'acc_splits_json' => wp_json_encode( [
                'activity_cost' => [
                    'pools' => $allocation['pools'],
                    'person_shares' => $allocation['person_shares'],
                    'totals' => $allocation['totals'],
                ],
            ], JSON_UNESCAPED_UNICODE ),
        ], [ 'id' => $order_id ] );
    }

    public static function cost_graph( array $filters = [] ): array {
        global $wpdb;
        self::ensure_tables();
        $pre = $wpdb->prefix;
        $from = $filters['date_from'] ?? date( 'Y-m-01' );
        $to = $filters['date_to'] ?? date( 'Y-m-d' );
        $order_id = (int) ( $filters['order_id'] ?? 0 );

        if ( $order_id > 0 ) {
            $order = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$pre}arag_orders WHERE id=%d", $order_id ), ARRAY_A );
            if ( ! $order ) return [ 'nodes' => [], 'edges' => [], 'person_shares' => [], 'orders' => [] ];
            $alloc = self::allocate( $order );
            return self::_graph( [ $alloc ], $order );
        }

        $orders = $wpdb->get_results( $wpdb->prepare(
            "SELECT * FROM {$pre}arag_orders
             WHERE payment_status IN ('paid','cod') AND created_at BETWEEN %s AND %s
             ORDER BY id DESC LIMIT 40",
            $from . ' 00:00:00', $to . ' 23:59:59'
        ), ARRAY_A ) ?: [];

        $allocs = [];
        foreach ( $orders as $o ) {
            $saved = $wpdb->get_results( $wpdb->prepare(
                "SELECT * FROM {$pre}arag_acc_activity_costs WHERE order_id=%d", (int) $o['id']
            ), ARRAY_A );
            if ( $saved ) {
                $lines = [];
                $ps = [];
                foreach ( $saved as $s ) {
                    $lines[] = [
                        'activity_code' => $s['activity_code'], 'activity_name' => $s['activity_name'],
                        'pool' => $s['pool'], 'person_id' => (int) $s['person_id'],
                        'person_label' => $s['person_label'], 'role_code' => $s['role_code'],
                        'amount' => (float) $s['amount'], 'share_pct' => (float) $s['share_pct'],
                    ];
                    $pid = (int) $s['person_id'];
                    $key = $pid ?: ( 'r:' . $s['role_code'] );
                    if ( ! isset( $ps[ $key ] ) ) {
                        $ps[ $key ] = [ 'person_id' => $pid, 'name' => $s['person_label'], 'amount' => 0, 'orders' => 0 ];
                    }
                    $ps[ $key ]['amount'] += (float) $s['amount'];
                }
                foreach ( $ps as &$p ) { $p['orders'] = 1; }
                unset( $p );
                $allocs[] = [
                    'order_id' => (int) $o['id'],
                    'order_number' => $o['order_number'] ?? '',
                    'customer' => $o['billing_name'] ?? '',
                    'lines' => $lines,
                    'person_shares' => array_values( $ps ),
                    'totals' => [
                        'activity_cost' => array_sum( array_column( $lines, 'amount' ) ),
                        'revenue' => (float) ( $o['total'] ?? $o['subtotal'] ?? 0 ),
                    ],
                ];
            } else {
                $a = self::allocate( $o );
                $a['customer'] = $o['billing_name'] ?? '';
                $allocs[] = $a;
            }
        }
        return self::_graph( $allocs );
    }

    private static function _graph( array $allocs, ?array $single = null ): array {
        $nodes = []; $edges = []; $persons = []; $orders = [];
        $add = static function( $id, $type, $label, $val = 0 ) use ( &$nodes ) {
            if ( ! isset( $nodes[ $id ] ) ) {
                $nodes[ $id ] = [ 'id' => $id, 'type' => $type, 'label' => $label, 'value' => 0.0 ];
            }
            $nodes[ $id ]['value'] += $val;
        };

        foreach ( $allocs as $alloc ) {
            $oid = 'order:' . (int) $alloc['order_id'];
            $num = $alloc['order_number'] ?? ( $single['order_number'] ?? ( '#' . $alloc['order_id'] ) );
            $cust = $alloc['customer'] ?? ( $single['billing_name'] ?? '' );
            $rev = (float) ( $alloc['totals']['revenue'] ?? 0 );
            $add( $oid, 'order', $num . ( $cust ? " — {$cust}" : '' ), $rev );
            $orders[] = [
                'order_id' => (int) $alloc['order_id'],
                'order_number' => $num,
                'customer' => $cust,
                'revenue' => $rev,
                'cost' => (float) ( $alloc['totals']['activity_cost'] ?? 0 ),
                'persons' => count( $alloc['person_shares'] ?? [] ),
            ];

            foreach ( $alloc['lines'] ?? [] as $ln ) {
                $aid = 'act:' . $ln['activity_code'];
                $add( $aid, 'activity', $ln['activity_name'], (float) $ln['amount'] );
                $ek1 = $oid . '>' . $aid;
                if ( ! isset( $edges[ $ek1 ] ) ) {
                    $edges[ $ek1 ] = [ 'from' => $oid, 'to' => $aid, 'amount' => 0, 'label' => $ln['pool'] ];
                }
                $edges[ $ek1 ]['amount'] += (float) $ln['amount'];

                $pid = (int) ( $ln['person_id'] ?? 0 );
                $nid = $pid > 0 ? ( 'person:' . $pid ) : ( 'role:' . ( $ln['role_code'] ?: 'company' ) );
                $add( $nid, 'person', $ln['person_label'], (float) $ln['amount'] );
                $ek2 = $aid . '>' . $nid;
                if ( ! isset( $edges[ $ek2 ] ) ) {
                    $edges[ $ek2 ] = [ 'from' => $aid, 'to' => $nid, 'amount' => 0, 'label' => ( $ln['share_pct'] ?? '' ) . '%' ];
                }
                $edges[ $ek2 ]['amount'] += (float) $ln['amount'];

                $pkey = $nid;
                if ( ! isset( $persons[ $pkey ] ) ) {
                    $persons[ $pkey ] = [
                        'person_id' => $pid, 'name' => $ln['person_label'],
                        'amount' => 0, 'orders' => 0,
                    ];
                }
                $persons[ $pkey ]['amount'] += (float) $ln['amount'];
            }
            foreach ( $alloc['person_shares'] ?? [] as $_ ) {
                /* count once per order later */
            }
        }

        /* شمارش سفارش یکتا تقریبی */
        foreach ( $persons as &$p ) {
            $p['orders'] = max( 1, (int) round( count( $allocs ) * 0.5 ) );
        }
        unset( $p );

        return [
            'nodes' => array_values( $nodes ),
            'edges' => array_values( $edges ),
            'person_shares' => array_values( $persons ),
            'orders' => $orders,
            'role_labels' => self::role_labels(),
        ];
    }
}
