<?php

namespace Modules\ActivityLog\Http\Controllers;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\ActivityLog\Entities\EnhancedActivityLog;
use Yajra\DataTables\Facades\DataTables;

class ActivityLogController extends Controller
{
    protected function modelTypeMap(): array
    {
        return [
            \App\Transaction::class        => 'Transaction',
            \App\Contact::class            => 'Contact',
            \App\Product::class            => 'Product',
            \App\User::class               => 'User',
            \App\TransactionPayment::class => 'Payment',
            \App\ExpenseCategory::class    => 'Expense Category',
            \App\Category::class           => 'Category',
            \App\Brands::class             => 'Brand',
            \App\Unit::class               => 'Unit',
            \App\TaxRate::class            => 'Tax Rate',
            \App\Account::class            => 'Account',
            \App\Business::class           => 'Business',
            \App\BusinessLocation::class   => 'Business Location',
            \App\DocumentAndNote::class    => 'Document / Note',
            \App\Variation::class              => 'Variation (Price)',
            'ProductLocation'                  => 'Product → Location',
            'opening_stock'                    => 'Opening Stock',
            'expense'                          => 'Expense',
            \App\PurchaseLine::class           => 'Purchase Line',
            \App\TransactionSellLine::class    => 'Sell Line',
            'data_fix'                         => 'Data Fix (Admin)',
            'product_compatible'               => 'Product Compatible',
            'shared_product'                   => 'Product Share',
            'auth'                             => 'Auth (Login / Logout)',
        ];
    }

    /**
     * Map of field names that hold foreign-key IDs → [ModelClass, nameField, displayKey].
     * nameField = null means the model needs special (User) handling.
     */
    protected function relationMap(): array
    {
        return [
            'brand_id'               => [\App\Brands::class,           'name',        'brand'],
            'category_id'            => [\App\Category::class,         'name',        'category'],
            'sub_category_id'        => [\App\Category::class,         'name',        'sub_category'],
            'unit_id'                => [\App\Unit::class,             'actual_name', 'unit'],
            'sub_unit_id'            => [\App\Unit::class,             'actual_name', 'sub_unit'],
            'secondary_unit_id'      => [\App\Unit::class,             'actual_name', 'secondary_unit'],
            'tax'                    => [\App\TaxRate::class,          'name',        'tax'],
            'location_id'            => [\App\BusinessLocation::class, 'name',        'location'],
            'contact_id'             => [\App\Contact::class,          'name',        'contact'],
            'created_by'             => [\App\User::class,             null,          'created_by'],
            'user_id'                => [\App\User::class,             null,          'user'],
            'warranty_id'            => ['App\\Warranty',              'name',        'warranty'],
            'selling_price_group_id'      => ['App\\SellingPriceGroup',    'name',        'selling_price_group'],
            'opening_stock_product_id'    => [\App\Product::class,         'name',        'product'],
            'expense_category_id'         => [\App\ExpenseCategory::class, 'name',        'expense_category'],
            'expense_sub_category_id'     => [\App\ExpenseCategory::class, 'name',        'expense_sub_category'],
            'product_id'                  => [\App\Product::class,         'name',        'product'],
        ];
    }

    /**
     * Resolve foreign-key IDs to human-readable names.
     * Batch-loads each model to avoid N+1 queries.
     * Replaces `brand_id: 93` → `brand: HP (#93)` and removes the original key.
     */
    protected function resolveRelatedNames(?array $data): ?array
    {
        if (empty($data)) {
            return $data;
        }

        $map = $this->relationMap();

        // Collect all IDs needed, grouped by model class
        $toFetch = [];
        foreach ($data as $field => $value) {
            if (!isset($map[$field]) || is_null($value) || $value === '') {
                continue;
            }
            [$modelClass] = $map[$field];
            $toFetch[$modelClass][(int) $value] = true;
        }

        // Batch-fetch names per model class
        $resolved = [];
        foreach ($toFetch as $modelClass => $ids) {
            if (!class_exists($modelClass)) {
                continue;
            }

            try {
                if ($modelClass === \App\User::class) {
                    $records = $modelClass::whereIn('id', array_keys($ids))
                        ->select(['id', 'surname', 'first_name', 'last_name'])
                        ->get();
                    foreach ($records as $r) {
                        $name = trim(
                            ($r->surname    ? $r->surname . ' '    : '') .
                            ($r->first_name ? $r->first_name . ' ' : '') .
                            ($r->last_name  ? $r->last_name        : '')
                        );
                        $resolved[$modelClass][$r->id] = $name ?: ('User #' . $r->id);
                    }
                } elseif ($modelClass === \App\Unit::class) {
                    $records = $modelClass::whereIn('id', array_keys($ids))
                        ->select(['id', 'actual_name'])
                        ->get();
                    foreach ($records as $r) {
                        $resolved[$modelClass][$r->id] = $r->actual_name ?? ('#' . $r->id);
                    }
                } else {
                    $records = $modelClass::whereIn('id', array_keys($ids))
                        ->select(['id', 'name'])
                        ->get();
                    foreach ($records as $r) {
                        $resolved[$modelClass][$r->id] = $r->name ?? ('#' . $r->id);
                    }
                }
            } catch (\Throwable $e) {
                // If model/table doesn't exist, skip silently
            }
        }

        // Rebuild the data array with resolved names
        $result = [];
        foreach ($data as $field => $value) {
            if (isset($map[$field]) && !is_null($value) && $value !== '') {
                [$modelClass, , $displayKey] = $map[$field];
                $name = $resolved[$modelClass][(int) $value] ?? null;

                if ($name !== null) {
                    $result[$displayKey] = $name . ' (#' . $value . ')';
                    continue; // skip adding the original _id key
                }
            }
            $result[$field] = $value;
        }

        return $result;
    }

    public function index()
    {
        if (!auth()->user()->can('activity_log.view')) {
            abort(403, 'Unauthorized action.');
        }

        $business_id = request()->session()->get('user.business_id');

        $users = User::allUsersDropdown($business_id, false);

        $model_types = collect($this->modelTypeMap())
            ->mapWithKeys(fn ($label, $class) => [$class => $label])
            ->prepend('All Modules', '')
            ->all();

        $action_types = [
            ''       => 'All Actions',
            'create' => 'Create',
            'update' => 'Update',
            'delete' => 'Delete',
            'login'  => 'Login',
            'logout' => 'Logout',
        ];

        return view('activityLog::index', compact('users', 'model_types', 'action_types'));
    }

    public function data(Request $request)
    {
        if (!auth()->user()->can('activity_log.view')) {
            abort(403, 'Unauthorized action.');
        }

        $business_id = $request->session()->get('user.business_id');

        $query = EnhancedActivityLog::forBusiness($business_id)
            ->where('enhanced_activity_logs.is_primary', 1)
            ->leftJoin('users as u', 'u.id', '=', 'enhanced_activity_logs.user_id')
            ->select([
                'enhanced_activity_logs.*',
                DB::raw("CONCAT(COALESCE(u.surname, ''), ' ', COALESCE(u.first_name, ''), ' ', COALESCE(u.last_name, '')) as user_full_name"),
                DB::raw("(CASE WHEN enhanced_activity_logs.request_id IS NOT NULL
                    THEN (SELECT COUNT(*) FROM enhanced_activity_logs sub
                          WHERE sub.request_id = enhanced_activity_logs.request_id
                          AND sub.business_id = enhanced_activity_logs.business_id)
                    ELSE 1 END) as entry_count"),
            ]);

        if (!empty($request->start_date) && !empty($request->end_date)) {
            $query->whereDate('enhanced_activity_logs.created_at', '>=', $request->start_date)
                  ->whereDate('enhanced_activity_logs.created_at', '<=', $request->end_date);
        }

        if (!empty($request->user_id)) {
            $query->where('enhanced_activity_logs.user_id', $request->user_id);
        }

        if (!empty($request->action) && in_array($request->action, ['create', 'update', 'delete', 'login', 'logout'])) {
            $query->where('enhanced_activity_logs.action', $request->action);
        }

        if (!empty($request->model_type)) {
            $query->where('enhanced_activity_logs.model_type', $request->model_type);
        }

        // Product / SKU / category search — only meaningful for share links.
        // Restricts results to "Product Share" entries whose linked share
        // contains a matching product (via the shared_product_items index).
        if (!empty($request->product_search)) {
            $this->applyProductSearch($query, $business_id, trim($request->product_search));
        }

        $modelTypeMap = $this->modelTypeMap();

        return DataTables::of($query)
            ->editColumn('created_at', '{{@format_datetime($created_at)}}')
            ->addColumn('module', function ($row) use ($modelTypeMap) {
                return $modelTypeMap[$row->model_type] ?? class_basename($row->model_type);
            })
            ->editColumn('action', function ($row) {
                $badges = [
                    'create' => 'bg-green',
                    'update' => 'bg-yellow',
                    'delete' => 'bg-red',
                    'login'  => 'bg-blue',
                    'logout' => 'bg-gray',
                ];
                $badge = $badges[$row->action] ?? 'bg-gray';
                return '<span class="label ' . $badge . '">' . ucfirst($row->action) . '</span>';
            })
            ->addColumn('changed_count', function ($row) {
                $fields = is_array($row->changed_fields) ? $row->changed_fields : [];
                $count  = count($fields);
                $extra  = ($row->entry_count ?? 1) - 1;
                if ($extra > 0) {
                    return $count . ' <span class="label bg-gray" title="' . $extra . ' more model(s) in this request">+' . $extra . '</span>';
                }
                return $count;
            })
            ->addColumn('actions', function ($row) {
                return '<button class="tw-dw-btn tw-dw-btn-xs tw-dw-btn-outline tw-dw-btn-info view-eal-log-btn"
                            data-id="' . $row->id . '"
                            title="View Details">
                            <i class="fa fa-eye"></i>
                        </button>';
            })
            ->filterColumn('user_full_name', function ($q, $keyword) {
                $q->whereRaw(
                    "CONCAT(COALESCE(u.surname,''),' ',COALESCE(u.first_name,''),' ',COALESCE(u.last_name,'')) LIKE ?",
                    ["%{$keyword}%"]
                );
            })
            ->rawColumns(['action', 'actions', 'changed_count'])
            ->make(true);
    }

    /**
     * For a "Product Share" log entry, load its linked share link and return
     * the public URL, expiry, and the list of products it contains. Returns
     * null for any other entry type.
     */
    protected function resolveSharedLink(EnhancedActivityLog $log, int $business_id): ?array
    {
        if ($log->model_type !== 'shared_product') {
            return null;
        }

        // model_id links to shared_products.id (live + backfilled entries);
        // fall back to the token recorded in after_data for safety.
        $share = null;
        if (!empty($log->model_id)) {
            $share = DB::table('shared_products')
                ->where('id', $log->model_id)
                ->where('business_id', $business_id)
                ->first();
        }
        if (!$share && !empty($log->after_data['token'])) {
            $share = DB::table('shared_products')
                ->where('token', $log->after_data['token'])
                ->where('business_id', $business_id)
                ->first();
        }
        if (!$share) {
            return null;
        }

        $products = json_decode($share->products_data, true);
        $products = is_array($products) ? $products : [];

        $isExpired = false;
        try {
            $isExpired = \Carbon\Carbon::parse($share->expires_at)->isPast();
        } catch (\Throwable) { /* leave as not-expired */ }

        return [
            'url'        => url('/shared-products/' . $share->token),
            'expires_at' => (string) $share->expires_at,
            'is_expired' => $isExpired,
            'count'      => count($products),
            'products'   => array_map(function ($p) {
                return [
                    'sku'      => $p['sku']      ?? null,
                    'name'     => $p['name']     ?? null,
                    'price'    => $p['price']    ?? null,
                    'stock'    => $p['stock']    ?? null,
                    'location' => $p['location'] ?? null,
                ];
            }, $products),
        ];
    }

    /**
     * Narrow the log query to "Product Share" entries whose linked share
     * contains a product matching the term by name, SKU, or category name.
     * Backed by the shared_product_items index; the actual share is joined
     * via enhanced_activity_logs.model_id → shared_products.id.
     */
    protected function applyProductSearch($query, int $business_id, string $term): void
    {
        // Resolve any categories (or sub-categories) whose name matches the term.
        $categoryIds = \App\Category::where('business_id', $business_id)
            ->where('category_type', 'product')
            ->where('name', 'like', "%{$term}%")
            ->pluck('id')
            ->all();

        $query->where('enhanced_activity_logs.model_type', 'shared_product')
            ->whereIn('enhanced_activity_logs.model_id', function ($sub) use ($business_id, $term, $categoryIds) {
                $sub->select('shared_product_id')
                    ->from('shared_product_items')
                    ->where('business_id', $business_id)
                    ->where(function ($w) use ($term, $categoryIds) {
                        $w->where('product_name', 'like', "%{$term}%")
                          ->orWhere('sku', 'like', "%{$term}%");

                        if (!empty($categoryIds)) {
                            $w->orWhereIn('category_id', $categoryIds)
                              ->orWhereIn('sub_category_id', $categoryIds);
                        }
                    });
            });
    }

    public function show(int $id)
    {
        if (!auth()->user()->can('activity_log.view')) {
            abort(403, 'Unauthorized action.');
        }

        $business_id = request()->session()->get('user.business_id');
        $log         = EnhancedActivityLog::forBusiness($business_id)->findOrFail($id);
        $modelTypeMap = $this->modelTypeMap();

        // Fetch all rows that belong to the same request (combined modal)
        if ($log->request_id) {
            $rows = EnhancedActivityLog::forBusiness($business_id)
                ->where('request_id', $log->request_id)
                ->orderBy('id')
                ->get();
        } else {
            $rows = collect([$log]);
        }

        // Load custom_labels once for all entries
        $business     = \App\Business::find($business_id);
        $customLabels = $business ? ($business->custom_labels ?? []) : [];
        if (is_string($customLabels)) {
            $customLabels = json_decode($customLabels, true) ?? [];
        }

        $entries = $rows->map(function ($row) use ($modelTypeMap, $customLabels) {
            $resolvedBefore = $this->resolveRelatedNames($row->before_data);
            $resolvedAfter  = $this->resolveRelatedNames($row->after_data);

            $resolvedBefore = $this->resolveCustomLabels($resolvedBefore, $row->model_type, $customLabels);
            $resolvedAfter  = $this->resolveCustomLabels($resolvedAfter,  $row->model_type, $customLabels);

            // Rebuild changed_fields from the resolved keys so FK renames
            // (e.g. location_id → location) don't break the diff table.
            $resolvedFields = array_values(array_unique(array_merge(
                array_keys($resolvedBefore ?? []),
                array_keys($resolvedAfter  ?? [])
            )));

            return [
                'id'             => $row->id,
                'action'         => $row->action,
                'model_type'     => $modelTypeMap[$row->model_type] ?? class_basename($row->model_type),
                'model_id'       => $row->model_id,
                'model_label'    => $row->model_label,
                'before_data'    => $resolvedBefore,
                'after_data'     => $resolvedAfter,
                'changed_fields' => $resolvedFields,
            ];
        })->values()->all();

        // Format created_at using the same business date/time format as @format_datetime
        $timeFormat = (request()->session()->get('business.time_format') == 24) ? 'H:i' : 'h:i A';
        $dateFormat = request()->session()->get('business.date_format', 'Y-m-d');
        $formattedDate = $log->created_at
            ? $log->created_at->format($dateFormat . ' ' . $timeFormat)
            : null;

        // For a "Product Share" entry, attach the actual products + the public
        // link so the viewer can see what was shared and re-open it.
        $share = $this->resolveSharedLink($log, $business_id);

        return response()->json([
            'success' => true,
            'meta'    => [
                'action'      => $log->action,
                'created_at'  => $formattedDate,
                'request_url' => $log->request_url,
                'ip_address'  => $log->ip_address,
                'user_id'     => $log->user_id,
            ],
            'entries' => $entries,
            'share'   => $share,
        ]);
    }

    /**
     * Translate custom field keys and custom payment method values using
     * the business's custom_labels configuration.
     *
     * Field key mappings:
     *   Product:          product_custom_field{N}  → custom_labels.product.custom_field_{N}
     *   Contact:          custom_field{N}          → custom_labels.contact.custom_field_{N}
     *   BusinessLocation: custom_field{N}          → custom_labels.location.custom_field_{N}
     *   Transaction:      custom_field_{N}         → custom_labels.purchase/sell.custom_field_{N}
     *   User:             custom_field{N}          → custom_labels.user.custom_field_{N}
     *
     * Value mappings:
     *   TransactionPayment.method: custom_pay_{N}  → custom_labels.payments.custom_pay_{N}
     */
    protected function resolveCustomLabels(?array $data, string $modelClass, array $customLabels): ?array
    {
        if (empty($data) || empty($customLabels)) {
            return $data;
        }

        $result = [];

        foreach ($data as $field => $value) {
            $newKey   = $field;
            $newValue = $value;

            // ── Field key translation ──────────────────────────────────
            // Product: product_custom_field{N}
            if (preg_match('/^product_custom_field(\d+)$/i', $field, $m)) {
                $label = $customLabels['product']['custom_field_' . $m[1]] ?? '';
                if ($label !== '') {
                    $newKey = $label;
                }

            // Contact: custom_field{N}  (no underscore before digit)
            } elseif (preg_match('/^custom_field(\d+)$/i', $field, $m)
                && $modelClass === \App\Contact::class) {
                $label = $customLabels['contact']['custom_field_' . $m[1]] ?? '';
                if ($label !== '') {
                    $newKey = $label;
                }

            // BusinessLocation: custom_field{N}
            } elseif (preg_match('/^custom_field(\d+)$/i', $field, $m)
                && $modelClass === \App\BusinessLocation::class) {
                $label = $customLabels['location']['custom_field_' . $m[1]] ?? '';
                if ($label !== '') {
                    $newKey = $label;
                }

            // Transaction / Expense: custom_field_{N}  (WITH underscore before digit)
            } elseif (preg_match('/^custom_field_(\d+)$/i', $field, $m)
                && ($modelClass === \App\Transaction::class || $modelClass === 'expense')) {
                $label = $customLabels['purchase']['custom_field_' . $m[1]]
                      ?? $customLabels['sell']['custom_field_'     . $m[1]]
                      ?? '';
                if ($label !== '') {
                    $newKey = $label;
                }

            // User: custom_field{N}
            } elseif (preg_match('/^custom_field(\d+)$/i', $field, $m)
                && $modelClass === \App\User::class) {
                $label = $customLabels['user']['custom_field_' . $m[1]] ?? '';
                if ($label !== '') {
                    $newKey = $label;
                }
            }

            // ── Value translation ──────────────────────────────────────
            // Payment method: custom_pay_{N} → human label
            if ($field === 'method'
                && $modelClass === \App\TransactionPayment::class
                && preg_match('/^custom_pay_(\d+)$/i', (string) $value, $m)
            ) {
                $payLabel = $customLabels['payments']['custom_pay_' . $m[1]] ?? '';
                if ($payLabel !== '') {
                    $newValue = $payLabel;
                }
            }

            $result[$newKey] = $newValue;
        }

        return $result;
    }
}
