<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\FileStore;

class PaymentService
{
    private FileStore $orders;
    private FileStore $payments;
    private FileStore $proofs;
    private CourseService $courses;
    private EnrollmentService $enrollments;
    private AuthService $auth;
    private ActivityLogService $logs;
    private InvoiceService $invoices;
    private CommissionService $commissions;
    private PromotionService $promotions;

    public function __construct()
    {
        $this->orders = new FileStore(storage_path('data/orders.json'));
        $this->payments = new FileStore(storage_path('data/payments.json'));
        $this->proofs = new FileStore(storage_path('data/payment_proofs.json'));
        $this->courses = new CourseService();
        $this->enrollments = new EnrollmentService();
        $this->auth = new AuthService();
        $this->logs = new ActivityLogService();
        $this->invoices = new InvoiceService();
        $this->commissions = new CommissionService();
        $this->promotions = new PromotionService();
    }

    public function startCheckout(int $courseId, array $user, string $couponCode = '', string $affiliateCode = ''): array
    {
        if (($user['role'] ?? '') !== 'student') {
            return ['ok' => false, 'error' => 'Apenas alunos podem comprar cursos.'];
        }

        $course = $this->courses->find($courseId);

        if (!$course || ($course['status'] ?? '') !== 'published') {
            return ['ok' => false, 'error' => 'Curso indisponivel.'];
        }

        if (!empty($course['is_free'])) {
            return ['ok' => false, 'error' => 'Este curso e gratuito. Use inscricao gratuita.'];
        }

        if ($this->enrollments->hasAccess((int) $user['id'], $courseId)) {
            return ['ok' => false, 'error' => 'Ja tem acesso a este curso.'];
        }

        $existing = $this->pendingPaymentForCourse((int) $user['id'], $courseId);

        if ($existing) {
            return ['ok' => true, 'order' => $existing['order'], 'payment' => $existing['payment']];
        }

        $pricing = $this->promotions->pricingForCourse($course, $couponCode ?: null);
        $amount = (float) $pricing['total'];
        $coupon = $pricing['coupon'];
        $order = $this->orders->insert([
            'student_id' => (int) $user['id'],
            'course_id' => $courseId,
            'order_number' => 'ORD-' . date('Ymd') . '-' . strtoupper(substr(bin2hex(random_bytes(4)), 0, 8)),
            'course_title' => $course['title'],
            'subtotal' => (float) $course['price'],
            'promotion_discount' => (float) $pricing['promotion_discount'],
            'coupon_code' => $coupon['code'] ?? '',
            'coupon_discount' => (float) $pricing['coupon_discount'],
            'discount_total' => max(0, (float) $course['price'] - $amount),
            'affiliate_code' => strtoupper(trim($affiliateCode)),
            'total' => $amount,
            'currency' => 'MZN',
            'status' => 'pending',
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $payment = $this->payments->insert([
            'order_id' => (int) $order['id'],
            'student_id' => (int) $user['id'],
            'course_id' => $courseId,
            'method' => 'bank_transfer',
            'amount' => $amount,
            'currency' => 'MZN',
            'reference' => 'PAY-' . strtoupper(substr(bin2hex(random_bytes(5)), 0, 10)),
            'status' => 'pending',
            'student_note' => '',
            'admin_note' => '',
            'submitted_at' => null,
            'approved_at' => null,
            'approved_by' => null,
            'rejected_at' => null,
            'rejected_by' => null,
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->logs->log('checkout_started', "Pedido {$order['order_number']} criado.", (int) $user['id']);

        return ['ok' => true, 'order' => $order, 'payment' => $payment];
    }

    public function submitProof(int $paymentId, array $file, string $note, array $user): array
    {
        $payment = $this->payment($paymentId);

        if (!$payment || (int) $payment['student_id'] !== (int) $user['id']) {
            return ['ok' => false, 'error' => 'Pagamento nao encontrado.'];
        }

        if (!in_array($payment['status'], ['pending', 'proof_submitted', 'rejected'], true)) {
            return ['ok' => false, 'error' => 'Este pagamento ja nao aceita comprovativos.'];
        }

        $stored = $this->storeProof($file);

        if (!$stored['ok']) {
            return ['ok' => false, 'error' => $stored['error']];
        }

        $proof = $this->proofs->insert([
            'payment_id' => $paymentId,
            'file_path' => $stored['path'],
            'file_type' => $stored['mime_type'],
            'file_size' => $stored['size'],
            'original_name' => $stored['original_name'],
            'uploaded_at' => date('c'),
            'created_at' => date('c'),
        ]);

        $this->updatePayment($paymentId, [
            'status' => 'proof_submitted',
            'student_note' => trim($note),
            'submitted_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->logs->log('payment_proof_submitted', 'Comprovativo enviado.', (int) $user['id']);

        return ['ok' => true, 'proof' => $proof];
    }

    public function approve(int $paymentId, int $adminId, string $note = ''): array
    {
        $payment = $this->payment($paymentId);

        if (!$payment) {
            return ['ok' => false, 'error' => 'Pagamento nao encontrado.'];
        }

        if (!in_array($payment['status'], ['proof_submitted', 'pending'], true)) {
            return ['ok' => false, 'error' => 'Pagamento nao pode ser aprovado neste estado.'];
        }

        $payment = $this->updatePayment($paymentId, [
            'status' => 'approved',
            'admin_note' => trim($note),
            'approved_at' => date('c'),
            'approved_by' => $adminId,
            'updated_at' => date('c'),
        ]);

        $this->updateOrder((int) $payment['order_id'], [
            'status' => 'paid',
            'updated_at' => date('c'),
        ]);

        $this->enrollments->enrollPaid((int) $payment['course_id'], (int) $payment['student_id'], (int) $payment['order_id'], (int) $payment['id']);
        $order = $this->order((int) $payment['order_id']) ?? [];
        $this->invoices->createForPayment($payment, $order);
        $this->commissions->createForPayment($payment);
        $this->promotions->registerCouponUse($order['coupon_code'] ?? '');
        $this->promotions->registerAffiliateSale($payment, $order);
        $this->logs->log('payment_approved', 'Pagamento aprovado e acesso liberado.', $adminId);

        return ['ok' => true, 'payment' => $payment];
    }

    public function reject(int $paymentId, int $adminId, string $note = ''): array
    {
        $payment = $this->payment($paymentId);

        if (!$payment) {
            return ['ok' => false, 'error' => 'Pagamento nao encontrado.'];
        }

        $payment = $this->updatePayment($paymentId, [
            'status' => 'rejected',
            'admin_note' => trim($note),
            'rejected_at' => date('c'),
            'rejected_by' => $adminId,
            'updated_at' => date('c'),
        ]);

        $this->logs->log('payment_rejected', 'Pagamento rejeitado.', $adminId);

        return ['ok' => true, 'payment' => $payment];
    }

    public function studentPayments(int $studentId): array
    {
        return array_values(array_map(
            fn (array $payment): array => $this->hydratePayment($payment),
            array_filter($this->payments->all(), fn (array $payment): bool => (int) ($payment['student_id'] ?? 0) === $studentId)
        ));
    }

    public function adminPayments(array $filters = []): array
    {
        $status = $filters['status'] ?? '';
        $search = strtolower(trim($filters['search'] ?? ''));

        $payments = array_filter($this->payments->all(), function (array $payment) use ($status, $search): bool {
            if ($status !== '' && ($payment['status'] ?? '') !== $status) {
                return false;
            }

            if ($search !== '') {
                $hydrated = $this->hydratePayment($payment);
                $haystack = strtolower($hydrated['student_name'] . ' ' . $hydrated['course_title'] . ' ' . $hydrated['reference']);

                if (!str_contains($haystack, $search)) {
                    return false;
                }
            }

            return true;
        });

        $payments = array_map(fn (array $payment): array => $this->hydratePayment($payment), $payments);
        usort($payments, fn (array $a, array $b): int => strcmp($b['updated_at'] ?? '', $a['updated_at'] ?? ''));

        return array_values($payments);
    }

    public function payment(int $paymentId): ?array
    {
        foreach ($this->payments->all() as $payment) {
            if ((int) ($payment['id'] ?? 0) === $paymentId) {
                return $this->hydratePayment($payment);
            }
        }

        return null;
    }

    public function order(int $orderId): ?array
    {
        foreach ($this->orders->all() as $order) {
            if ((int) ($order['id'] ?? 0) === $orderId) {
                return $order;
            }
        }

        return null;
    }

    public function proof(int $proofId): ?array
    {
        foreach ($this->proofs->all() as $proof) {
            if ((int) ($proof['id'] ?? 0) === $proofId) {
                return $proof;
            }
        }

        return null;
    }

    public function latestProofForPayment(int $paymentId): ?array
    {
        $proofs = array_values(array_filter($this->proofs->all(), fn (array $proof): bool => (int) ($proof['payment_id'] ?? 0) === $paymentId));

        return $proofs ? end($proofs) : null;
    }

    public function proofPath(array $proof): string
    {
        return storage_path((string) $proof['file_path']);
    }

    private function pendingPaymentForCourse(int $studentId, int $courseId): ?array
    {
        foreach ($this->payments->all() as $payment) {
            if ((int) ($payment['student_id'] ?? 0) === $studentId
                && (int) ($payment['course_id'] ?? 0) === $courseId
                && in_array($payment['status'] ?? '', ['pending', 'proof_submitted', 'rejected'], true)
            ) {
                return [
                    'payment' => $this->hydratePayment($payment),
                    'order' => $this->order((int) $payment['order_id']),
                ];
            }
        }

        return null;
    }

    private function hydratePayment(array $payment): array
    {
        $student = $this->auth->findById((int) ($payment['student_id'] ?? 0));
        $course = $this->courses->find((int) ($payment['course_id'] ?? 0));
        $order = $this->order((int) ($payment['order_id'] ?? 0));

        $payment['student_name'] = $student['name'] ?? 'Aluno';
        $payment['student_email'] = $student['email'] ?? '';
        $payment['course_title'] = $course['title'] ?? ($order['course_title'] ?? 'Curso');
        $payment['order_number'] = $order['order_number'] ?? '';
        $payment['proof'] = $this->latestProofForPayment((int) ($payment['id'] ?? 0));

        return $payment;
    }

    private function updatePayment(int $paymentId, array $changes): array
    {
        $records = $this->payments->all();
        $updated = [];

        foreach ($records as $index => $payment) {
            if ((int) ($payment['id'] ?? 0) === $paymentId) {
                $records[$index] = array_merge($payment, $changes);
                $updated = $records[$index];
                break;
            }
        }

        $this->payments->save($records);

        return $this->hydratePayment($updated);
    }

    private function updateOrder(int $orderId, array $changes): void
    {
        $records = $this->orders->all();

        foreach ($records as $index => $order) {
            if ((int) ($order['id'] ?? 0) === $orderId) {
                $records[$index] = array_merge($order, $changes);
                break;
            }
        }

        $this->orders->save($records);
    }

    private function storeProof(array $file): array
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return ['ok' => false, 'error' => 'Envie o comprovativo.'];
        }

        if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
            return ['ok' => false, 'error' => 'Falha no upload do comprovativo.'];
        }

        if ((int) ($file['size'] ?? 0) > 10 * 1024 * 1024) {
            return ['ok' => false, 'error' => 'Comprovativo acima de 10MB.'];
        }

        $mimeType = mime_content_type((string) $file['tmp_name']) ?: ($file['type'] ?? '');
        $allowed = ['application/pdf', 'image/jpeg', 'image/png'];

        if (!in_array($mimeType, $allowed, true)) {
            return ['ok' => false, 'error' => 'Comprovativo deve ser PDF, JPG ou PNG.'];
        }

        $extension = strtolower(pathinfo((string) $file['name'], PATHINFO_EXTENSION));
        $safeName = bin2hex(random_bytes(16)) . ($extension ? '.' . $extension : '');
        $relativePath = 'proofs' . DIRECTORY_SEPARATOR . $safeName;
        $destination = storage_path($relativePath);

        if (!is_dir(dirname($destination))) {
            mkdir(dirname($destination), 0775, true);
        }

        if (!move_uploaded_file((string) $file['tmp_name'], $destination)) {
            return ['ok' => false, 'error' => 'Nao foi possivel guardar o comprovativo.'];
        }

        return [
            'ok' => true,
            'path' => $relativePath,
            'original_name' => (string) $file['name'],
            'mime_type' => $mimeType,
            'size' => (int) $file['size'],
        ];
    }
}
