<?php

namespace App\Http\Controllers;

use App\Services\ActivityLogger;
use App\Traits\NotifiesAdmins;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Log;

abstract class BaseCrudController extends Controller
{
    use AuthorizesRequests, NotifiesAdmins;

    /**
     * The repository interface for this controller
     */
    protected $repository;

    /**
     * The model class for authorization
     */
    protected $modelClass;

    /**
     * The resource class for API responses
     */
    protected $resourceClass;

    /**
     * The request class for validation
     */
    protected $requestClass;

    /**
     * The name of the resource for logging (e.g., 'category', 'model')
     */
    protected $resourceName;

    /**
     * The plural name of the resource for messages
     */
    protected $resourceNamePlural;

    /**
     * Constructor
     */
    public function __construct($repository)
    {
        $this->repository = $repository;
    }

    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): JsonResponse
    {
        $this->authorize('viewAny', $this->modelClass);

        try {
            $perPage = $request->get('per_page', 10) ?? 10;
            $perPage = min($perPage, 1000); // Limit max per page to 1000
            $search = (string) ($request->get('search', '') ?? '');
            $sortBy = (string) ($request->get('sort_by', 'id') ?? 'id');
            $sortOrder = (string) ($request->get('sort_order', 'asc') ?? 'asc');

            $items = $this->getIndexData($request, $perPage, $search, $sortBy, $sortOrder);

            // Check if the result is paginated
            if ($items instanceof LengthAwarePaginator) {
                return response()->json([
                    'success' => true,
                    'data' => $this->resourceClass::collection($items->items()),
                    'pagination' => [
                        'current_page' => $items->currentPage(),
                        'per_page' => $items->perPage(),
                        'total' => $items->total(),
                        'last_page' => $items->lastPage(),
                        'from' => $items->firstItem(),
                        'to' => $items->lastItem(),
                        'has_more_pages' => $items->hasMorePages(),
                    ],
                    'message' => $this->getSuccessMessage('index'),
                ]);
            }

            // Fallback for non-paginated results
            return response()->json([
                'success' => true,
                'data' => $this->resourceClass::collection($items),
                'message' => $this->getSuccessMessage('index'),
            ]);
        } catch (Exception $e) {
            Log::error("Error fetching {$this->resourceNamePlural}: ".$e->getMessage());

            return $this->errorResponse($this->getErrorMessage('index'), 500);
        }
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): JsonResponse
    {
        $this->authorize('create', $this->modelClass);

        try {
            $validated = $this->validateRequest($request);
            $data = $this->prepareStoreData($validated);

            $item = $this->repository->create($data);

            ActivityLogger::log("Created {$this->resourceName}: {$this->getItemName($item)}", $item);

            return response()->json([
                'success' => true,
                'data' => new $this->resourceClass($item),
                'message' => $this->getSuccessMessage('store'),
            ], 201);

        } catch (Exception $e) {
            Log::error("Error creating {$this->resourceName}: ".$e->getMessage());

            return $this->errorResponse($this->getErrorMessage('store').': '.$e->getMessage(), 500);
        }
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id): JsonResponse
    {
        $this->authorize('view', $this->modelClass);

        try {
            $item = $this->repository->findById($id);

            if (! $item) {
                return $this->errorResponse($this->getNotFoundMessage(), 404);
            }

            $item = $this->loadShowRelations($item);

            return response()->json([
                'success' => true,
                'data' => new $this->resourceClass($item),
                'message' => $this->getSuccessMessage('show'),
            ]);
        } catch (Exception $e) {
            Log::error("Error fetching {$this->resourceName}: ".$e->getMessage());

            return $this->errorResponse($this->getErrorMessage('show'), 500);
        }
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id): JsonResponse
    {
        $this->authorize('update', $this->modelClass);

        try {
            $item = $this->repository->findById($id);

            if (! $item) {
                return $this->errorResponse($this->getNotFoundMessage(), 404);
            }

            $validated = $this->validateRequest($request);
            $data = $this->prepareUpdateData($validated, $item);

            $item->update($data);

            ActivityLogger::log("Updated {$this->resourceName}: {$this->getItemName($item)}", $item);

            return response()->json([
                'success' => true,
                'data' => new $this->resourceClass($item),
                'message' => $this->getSuccessMessage('update'),
            ]);

        } catch (Exception $e) {
            Log::error("Error updating {$this->resourceName}: ".$e->getMessage());

            return $this->errorResponse($this->getErrorMessage('update').': '.$e->getMessage(), 500);
        }
    }

    /**
     * Partially update the specified resource in storage (PATCH).
     */
    public function patch(Request $request, string $id): JsonResponse
    {
        // PATCH uses the same logic as update
        return $this->update($request, $id);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id): JsonResponse
    {
        $this->authorize('deleteAny', $this->modelClass);

        try {
            $item = $this->repository->findById($id);

            if (! $item) {
                return $this->errorResponse($this->getNotFoundMessage(), 404);
            }

            // Check for dependencies before deletion
            $dependencyCheck = $this->checkDependencies($item);
            if ($dependencyCheck !== null) {
                return $dependencyCheck;
            }

            $this->repository->delete($id);

            ActivityLogger::log("Deleted {$this->resourceName}: {$this->getItemName($item)}", $item);

            return response()->json([
                'success' => true,
                'message' => $this->getSuccessMessage('destroy'),
            ]);

        } catch (Exception $e) {
            Log::error("Error deleting {$this->resourceName}: ".$e->getMessage());

            return $this->errorResponse($this->getErrorMessage('destroy').': '.$e->getMessage(), 500);
        }
    }

    /**
     * Get data for index method - can be overridden by child classes
     */
    protected function getIndexData(Request $request, int $perPage = 10, string $search = '', string $sortBy = 'id', string $sortOrder = 'asc')
    {
        // Always try paginated methods first when perPage is specified
        if ($perPage > 0) {
            if (method_exists($this->repository, 'getAllWithRelationsPaginated')) {
                return $this->repository->getAllWithRelationsPaginated($perPage, $search, $sortBy, $sortOrder);
            }

            if (method_exists($this->repository, 'getAllPaginated')) {
                return $this->repository->getAllPaginated($perPage, $search, $sortBy, $sortOrder);
            }
        }

        // Fallback to non-paginated methods
        if (method_exists($this->repository, 'getAllWithRelations')) {
            return $this->repository->getAllWithRelations($search, $sortBy, $sortOrder);
        }

        return $this->repository->all($search, $sortBy, $sortOrder);
    }

    /**
     * Validate request data - can be overridden by child classes
     */
    protected function validateRequest(Request $request): array
    {
        if ($this->requestClass) {
            // For multipart/form-data, use direct validation to avoid FormRequest parsing issues
            if (str_contains($request->header('Content-Type', ''), 'multipart/form-data')) {
                return $request->validate(app($this->requestClass)->rules());
            }
            
            // Create FormRequest instance with the current request
            $formRequest = $this->requestClass::createFrom($request);
            $formRequest->setContainer(app());
            $formRequest->setRedirector(app('redirect'));
            
            // Validate the request
            $formRequest->validateResolved();
            
            return $formRequest->validated();
        }

        return $request->all();
    }

    /**
     * Prepare data for store method - can be overridden by child classes
     */
    protected function prepareStoreData(array $validated): array
    {
        return $validated;
    }

    /**
     * Prepare data for update method - can be overridden by child classes
     */
    protected function prepareUpdateData(array $validated, $item): array
    {
        return $validated;
    }

    /**
     * Load relations for show method - can be overridden by child classes
     */
    protected function loadShowRelations($item)
    {
        return $item;
    }

    /**
     * Check dependencies before deletion - can be overridden by child classes
     * Return null if deletion is allowed, or a JsonResponse if not
     */
    protected function checkDependencies($item): ?JsonResponse
    {
        return null;
    }

    /**
     * Get the name of the item for logging - can be overridden by child classes
     */
    protected function getItemName($item): string
    {
        return $item->name ?? $item->id;
    }

    /**
     * Get success message for different operations
     */
    protected function getSuccessMessage(string $operation): string
    {
        $messages = [
            'index' => ucfirst($this->resourceNamePlural).' berjaya dimuatkan',
            'store' => ucfirst($this->resourceName).' berjaya ditambah',
            'show' => ucfirst($this->resourceName).' berjaya dimuatkan',
            'update' => ucfirst($this->resourceName).' berjaya dikemaskini',
            'destroy' => ucfirst($this->resourceName).' berjaya dipadam',
        ];

        return $messages[$operation] ?? 'Operasi berjaya';
    }

    /**
     * Get error message for different operations
     */
    protected function getErrorMessage(string $operation): string
    {
        $messages = [
            'index' => 'Gagal memuatkan '.$this->resourceNamePlural,
            'store' => 'Gagal menambah '.$this->resourceName,
            'show' => 'Gagal memuatkan '.$this->resourceName,
            'update' => 'Gagal mengemaskini '.$this->resourceName,
            'destroy' => 'Gagal memadam '.$this->resourceName,
        ];

        return $messages[$operation] ?? 'Operasi gagal';
    }

    /**
     * Get not found message
     */
    protected function getNotFoundMessage(): string
    {
        return ucfirst($this->resourceName).' tidak dijumpai.';
    }

    /**
     * Return a standardized error response
     */
    protected function errorResponse(string $message, int $statusCode = 400): JsonResponse
    {
        return response()->json([
            'success' => false,
            'message' => $message,
        ], $statusCode);
    }
}
