<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\FileStore;

class PromotionService
{
    private FileStore $coupons;
    private FileStore $campaigns;
    private FileStore $affiliates;
    private FileStore $affiliateCommissions;
    private CourseService $courses;
    private AuthService $auth;
    private ActivityLogService $logs;

    public function __construct()
    {
        $this->coupons = new FileStore(storage_path('data/coupons.json'));
        $this->campaigns = new FileStore(storage_path('data/promotions.json'));
        $this->affiliates = new FileStore(storage_path('data/affiliates.json'));
        $this->affiliateCommissions = new FileStore(storage_path('data/affiliate_commissions.json'));
        $this->courses = new CourseService();
        $this->auth = new AuthService();
        $this->logs = new ActivityLogService();
    }

    public function createCoupon(array $data, int $adminId): array
    {
        $code = strtoupper(trim((string) ($data['code'] ?? '')));
        $type = (string) ($data['discount_type'] ?? 'percent');
        $value = (float) str_replace(',', '.', (string) ($data['discount_value'] ?? 0));

        if ($code === '' || !preg_match('/^[A-Z0-9_-]{3,30}$/', $code)) {
            return ['ok' => false, 'error' => 'Codigo do cupao invalido.'];
        }

        if ($this->couponByCode($code)) {
            return ['ok' => false, 'error' => 'Este cupao ja existe.'];
        }

        if (!in_array($type, ['percent', 'fixed'], true) || $value <= 0) {
            return ['ok' => false, 'error' => 'Desconto invalido.'];
        }

        if ($type === 'percent' && $value > 90) {
            return ['ok' => false, 'error' => 'Desconto percentual nao pode passar de 90%.'];
        }

        $coupon = $this->coupons->insert([
            'code' => $code,
            'description' => trim((string) ($data['description'] ?? '')),
            'discount_type' => $type,
            'discount_value' => $value,
            'course_id' => (int) ($data['course_id'] ?? 0),
            'max_uses' => max(0, (int) ($data['max_uses'] ?? 0)),
            'used_count' => 0,
            'starts_at' => trim((string) ($data['starts_at'] ?? '')),
            'ends_at' => trim((string) ($data['ends_at'] ?? '')),
            'status' => 'active',
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->logs->log('coupon_created', "Cupao {$code} criado.", $adminId);

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

    public function createCampaign(array $data, int $adminId): array
    {
        $courseId = (int) ($data['course_id'] ?? 0);
        $course = $this->courses->find($courseId);
        $discountPrice = (float) str_replace(',', '.', (string) ($data['discount_price'] ?? 0));

        if (!$course || !empty($course['is_free'])) {
            return ['ok' => false, 'error' => 'Escolha um curso pago valido.'];
        }

        if ($discountPrice <= 0 || $discountPrice >= (float) $course['price']) {
            return ['ok' => false, 'error' => 'Preco promocional deve ser maior que zero e menor que o preco normal.'];
        }

        $campaign = $this->campaigns->insert([
            'name' => trim((string) ($data['name'] ?? 'Promocao')),
            'course_id' => $courseId,
            'course_title' => $course['title'],
            'discount_price' => $discountPrice,
            'starts_at' => trim((string) ($data['starts_at'] ?? '')),
            'ends_at' => trim((string) ($data['ends_at'] ?? '')),
            'status' => 'active',
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->logs->log('campaign_created', "Promocao {$campaign['name']} criada.", $adminId);

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

    public function createAffiliate(array $data, int $adminId): array
    {
        $name = trim((string) ($data['name'] ?? ''));
        $email = trim((string) ($data['email'] ?? ''));
        $percentage = (float) str_replace(',', '.', (string) ($data['commission_percentage'] ?? 10));
        $code = strtoupper(trim((string) ($data['code'] ?? '')));

        if ($name === '' || $email === '') {
            return ['ok' => false, 'error' => 'Nome e email do afiliado sao obrigatorios.'];
        }

        if ($percentage <= 0 || $percentage > 50) {
            return ['ok' => false, 'error' => 'Comissao do afiliado deve estar entre 1% e 50%.'];
        }

        if ($code === '') {
            $code = $this->uniqueAffiliateCode($name);
        }

        if ($this->affiliateByCode($code)) {
            return ['ok' => false, 'error' => 'Codigo de afiliado ja existe.'];
        }

        $user = $this->auth->findByEmail($email);
        $affiliate = $this->affiliates->insert([
            'name' => $name,
            'email' => $email,
            'user_id' => (int) ($user['id'] ?? 0),
            'code' => $code,
            'commission_percentage' => $percentage,
            'status' => 'active',
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);

        $this->logs->log('affiliate_created', "Afiliado {$name} criado.", $adminId);

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

    public function pricingForCourse(array $course, ?string $couponCode = null): array
    {
        $base = (float) ($course['price'] ?? 0);
        $courseDiscount = course_effective_price($course);
        $campaign = $this->activeCampaignForCourse((int) ($course['id'] ?? 0));
        $campaignPrice = $campaign ? (float) $campaign['discount_price'] : $courseDiscount;
        $subtotalAfterPromotion = min($courseDiscount, $campaignPrice);
        $coupon = null;
        $couponDiscount = 0.0;

        if ($couponCode) {
            $coupon = $this->validCoupon($couponCode, (int) ($course['id'] ?? 0));

            if ($coupon) {
                $couponDiscount = $this->discountAmount($coupon, $subtotalAfterPromotion);
            }
        }

        $total = max(0, $subtotalAfterPromotion - $couponDiscount);

        return [
            'base_price' => $base,
            'promotion_price' => $subtotalAfterPromotion,
            'promotion_discount' => max(0, $base - $subtotalAfterPromotion),
            'coupon' => $coupon,
            'coupon_discount' => $couponDiscount,
            'total' => $total,
            'campaign' => $campaign,
        ];
    }

    public function registerCouponUse(?string $code): void
    {
        if (!$code) {
            return;
        }

        $records = $this->coupons->all();

        foreach ($records as $index => $coupon) {
            if (strtoupper((string) ($coupon['code'] ?? '')) === strtoupper($code)) {
                $records[$index]['used_count'] = (int) ($coupon['used_count'] ?? 0) + 1;
                $records[$index]['updated_at'] = date('c');
                break;
            }
        }

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

    public function registerAffiliateSale(array $payment, array $order): ?array
    {
        $affiliateCode = strtoupper(trim((string) ($order['affiliate_code'] ?? '')));
        $affiliate = $affiliateCode ? $this->affiliateByCode($affiliateCode) : null;

        if (!$affiliate || ($affiliate['status'] ?? '') !== 'active') {
            return null;
        }

        foreach ($this->affiliateCommissions->all() as $commission) {
            if ((int) ($commission['payment_id'] ?? 0) === (int) $payment['id']) {
                return $commission;
            }
        }

        $amount = round((float) $payment['amount'] * ((float) $affiliate['commission_percentage'] / 100), 2);

        return $this->affiliateCommissions->insert([
            'affiliate_id' => (int) $affiliate['id'],
            'affiliate_name' => $affiliate['name'],
            'affiliate_code' => $affiliate['code'],
            'payment_id' => (int) $payment['id'],
            'order_id' => (int) $payment['order_id'],
            'course_id' => (int) $payment['course_id'],
            'course_title' => $order['course_title'] ?? 'Curso',
            'gross_amount' => (float) $payment['amount'],
            'commission_percentage' => (float) $affiliate['commission_percentage'],
            'commission_amount' => $amount,
            'currency' => 'MZN',
            'status' => 'available',
            'created_at' => date('c'),
            'updated_at' => date('c'),
        ]);
    }

    public function coupons(): array
    {
        return array_reverse($this->coupons->all());
    }

    public function campaigns(): array
    {
        return array_reverse($this->campaigns->all());
    }

    public function affiliates(): array
    {
        return array_reverse($this->affiliates->all());
    }

    public function affiliateCommissions(): array
    {
        return array_reverse($this->affiliateCommissions->all());
    }

    private function validCoupon(string $code, int $courseId): ?array
    {
        $coupon = $this->couponByCode($code);

        if (!$coupon || ($coupon['status'] ?? '') !== 'active') {
            return null;
        }

        if ((int) ($coupon['course_id'] ?? 0) > 0 && (int) $coupon['course_id'] !== $courseId) {
            return null;
        }

        if ((int) ($coupon['max_uses'] ?? 0) > 0 && (int) ($coupon['used_count'] ?? 0) >= (int) $coupon['max_uses']) {
            return null;
        }

        return $this->isWithinDates($coupon) ? $coupon : null;
    }

    private function activeCampaignForCourse(int $courseId): ?array
    {
        foreach ($this->campaigns->all() as $campaign) {
            if ((int) ($campaign['course_id'] ?? 0) === $courseId && ($campaign['status'] ?? '') === 'active' && $this->isWithinDates($campaign)) {
                return $campaign;
            }
        }

        return null;
    }

    private function couponByCode(string $code): ?array
    {
        foreach ($this->coupons->all() as $coupon) {
            if (strtoupper((string) ($coupon['code'] ?? '')) === strtoupper($code)) {
                return $coupon;
            }
        }

        return null;
    }

    private function affiliateByCode(string $code): ?array
    {
        foreach ($this->affiliates->all() as $affiliate) {
            if (strtoupper((string) ($affiliate['code'] ?? '')) === strtoupper($code)) {
                return $affiliate;
            }
        }

        return null;
    }

    private function discountAmount(array $coupon, float $amount): float
    {
        if (($coupon['discount_type'] ?? '') === 'fixed') {
            return min($amount, (float) $coupon['discount_value']);
        }

        return round($amount * ((float) $coupon['discount_value'] / 100), 2);
    }

    private function isWithinDates(array $record): bool
    {
        $now = time();
        $startsAt = trim((string) ($record['starts_at'] ?? ''));
        $endsAt = trim((string) ($record['ends_at'] ?? ''));

        if ($startsAt !== '' && strtotime($startsAt) > $now) {
            return false;
        }

        if ($endsAt !== '' && strtotime($endsAt . ' 23:59:59') < $now) {
            return false;
        }

        return true;
    }

    private function uniqueAffiliateCode(string $name): string
    {
        $base = strtoupper(substr(preg_replace('/[^A-Za-z0-9]+/', '', $name) ?: 'AFILIADO', 0, 10));

        do {
            $code = $base . random_int(100, 999);
        } while ($this->affiliateByCode($code));

        return $code;
    }
}
