<?php

namespace App\Services;

use App\Jobs\SyncMikrotikRouterDataJob;
use App\Jobs\TestMikrotikConnectionJob;
use App\Models\ActivityLog;
use App\Models\Customer;
use App\Models\Mikrotik;
use App\Models\Package;
use App\Repositories\MikrotikRepository;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class MikrotikService
{
    public function __construct(
        protected MikrotikRepository $repository,
        protected MikrotikApiService $apiService,
        protected CustomerService $customerService,
    ) {}

    public function importRouterSecretsToSoftware(?int $onlyRouterId = null, ?string $onlyUsername = null): array
    {
        $summary = ['created' => 0, 'skipped' => 0, 'failed' => 0, 'messages' => []];

        $routers = Mikrotik::where('status', true)->when($onlyRouterId, fn ($query) => $query->whereKey($onlyRouterId))->get();
        foreach ($routers as $router) {
            try {
                $secrets = $this->apiService->fetchSecrets($router);
                Cache::put('mikrotik:secrets:'.$router->id, $secrets, now()->addDays(7));
            } catch (\Throwable $exception) {
                $summary['failed']++;
                $summary['messages'][] = $router->name.': '.$exception->getMessage();

                continue;
            }

            $packages = Package::where('status', true)
                ->whereNotNull('mikrotik_profile_name')
                ->where(fn ($query) => $query->whereNull('mikrotik_server_id')->orWhere('mikrotik_server_id', $router->id))
                ->orderByRaw('CASE WHEN mikrotik_server_id = ? THEN 0 ELSE 1 END DESC', [$router->id])
                ->get()
                ->keyBy(fn (Package $package) => strtolower(trim((string) $package->mikrotik_profile_name)));

            $existing = Customer::withTrashed()->where('mikrotik_id', $router->id)->get()
                ->flatMap(fn (Customer $customer) => [$customer->ip_address, $customer->customer_code, $customer->queue_password])
                ->filter()->map(fn ($value) => strtolower(trim((string) $value)))->flip();

            foreach ($secrets as $secret) {
                $username = trim((string) ($secret['name'] ?? ''));
                if ($onlyUsername !== null && strcasecmp($username, trim($onlyUsername)) !== 0) {
                    continue;
                }
                $profile = strtolower(trim((string) ($secret['profile'] ?? '')));
                $package = $packages->get($profile);

                if ($username === '' || $existing->has(strtolower($username))) {
                    $summary['skipped']++;

                    continue;
                }
                if (! $package) {
                    $summary['skipped']++;
                    $summary['messages'][] = $username.': software package/profile not found ('.($secret['profile'] ?? 'blank').')';

                    continue;
                }

                try {
                    $remoteAddress = trim((string) ($secret['remote_address'] ?? ''));
                    $softwareIdentifier = strlen($username) <= 45 ? $username : (strlen($remoteAddress) <= 45 ? $remoteAddress : null);
                    if (! $softwareIdentifier) {
                        throw new \RuntimeException('PPP username is too long and no usable remote address exists.');
                    }

                    $customer = $this->customerService->create([
                        'name' => $username,
                        'contact_phone' => 'N/A',
                        'ip_address' => $softwareIdentifier,
                        'package_id' => $package->id,
                        'monthly_bill' => $package->price,
                        'status' => empty($secret['disabled']) ? 1 : 0,
                        'connection_type' => 'PPPoE',
                        'mikrotik_id' => $router->id,
                        'queue_password' => data_get($secret, 'routeros_attrs.password'),
                        'connection_date' => now()->toDateString(),
                        'entry_date' => now()->toDateString(),
                        'created_by' => auth()->id(),
                        'remark' => 'Imported from MikroTik '.$router->name,
                    ]);
                    $existing->put(strtolower($username), $customer->id);
                    $summary['created']++;
                } catch (\Throwable $exception) {
                    $summary['failed']++;
                    $summary['messages'][] = $username.': '.$exception->getMessage();
                }
            }
        }

        $this->logBulkSync('mikrotik_secrets_imported', 'MikroTik to software import', $summary);

        return $summary;
    }

    public function uploadSoftwareCustomersToRouters(?int $onlyCustomerId = null): array
    {
        $summary = ['created' => 0, 'skipped' => 0, 'failed' => 0, 'messages' => []];

        $routerIds = $onlyCustomerId ? Customer::whereKey($onlyCustomerId)->pluck('mikrotik_id')->filter() : null;
        $routers = Mikrotik::where('status', true)->when($routerIds, fn ($query) => $query->whereIn('id', $routerIds))->get();
        foreach ($routers as $router) {
            try {
                $secrets = $this->apiService->fetchSecrets($router);
                $profiles = $this->apiService->fetchProfiles($router);
                Cache::put('mikrotik:secrets:'.$router->id, $secrets, now()->addDays(7));
                Cache::put('mikrotik:profiles:'.$router->id, $profiles, now()->addDays(7));
            } catch (\Throwable $exception) {
                $summary['failed']++;
                $summary['messages'][] = $router->name.': '.$exception->getMessage();

                continue;
            }

            $secretNames = collect($secrets)->pluck('name')->filter()->map(fn ($value) => strtolower(trim((string) $value)))->flip();
            $profileNames = collect($profiles)->pluck('name')->filter()->map(fn ($value) => strtolower(trim((string) $value)))->flip();
            $pending = [];

            $customers = Customer::with('package')->where('mikrotik_id', $router->id)->where('status', true)
                ->when($onlyCustomerId, fn ($query) => $query->whereKey($onlyCustomerId))->get();
            foreach ($customers as $customer) {
                $username = trim((string) ($customer->ip_address ?: $customer->customer_code));
                $profile = trim((string) $customer->package?->mikrotik_profile_name);
                $packageBelongsToRouter = ! $customer->package?->mikrotik_server_id || (int) $customer->package->mikrotik_server_id === $router->id;

                if ($username === '' || $secretNames->has(strtolower($username))) {
                    $summary['skipped']++;

                    continue;
                }
                if (! $customer->package || ! $packageBelongsToRouter || $profile === '' || ! $profileNames->has(strtolower($profile))) {
                    $summary['skipped']++;
                    $summary['messages'][] = ($customer->customer_code ?: $customer->name).': matching software package/RouterOS profile not found';

                    continue;
                }
                if (blank($customer->queue_password)) {
                    $summary['skipped']++;
                    $summary['messages'][] = ($customer->customer_code ?: $customer->name).': PPP password is empty';

                    continue;
                }

                $pending[] = [
                    'username' => $username,
                    'password' => $customer->queue_password,
                    'profile' => $profile,
                    'remote_address' => filter_var($customer->ip_address, FILTER_VALIDATE_IP) ? $customer->ip_address : null,
                    'disabled' => false,
                    'comment' => 'ISP Billing - '.($customer->customer_code ?: $customer->name),
                ];
                $secretNames->put(strtolower($username), true);
            }

            if ($pending) {
                try {
                    foreach ($this->apiService->addSecrets($router, $pending) as $result) {
                        if ($result['success']) {
                            $summary['created']++;
                        } else {
                            $summary['failed']++;
                            $summary['messages'][] = $result['username'].': '.$result['message'];
                        }
                    }
                    SyncMikrotikRouterDataJob::dispatch($router->id);
                } catch (\Throwable $exception) {
                    $summary['failed'] += count($pending);
                    $summary['messages'][] = $router->name.': '.$exception->getMessage();
                }
            }
        }

        $this->logBulkSync('software_customers_uploaded', 'Software to MikroTik upload', $summary);

        return $summary;
    }

    private function logBulkSync(string $action, string $description, array $summary): void
    {
        ActivityLog::create([
            'user_id' => auth()->id(),
            'action' => $action,
            'resource_type' => Mikrotik::class,
            'description' => $description.": {$summary['created']} created, {$summary['skipped']} skipped, {$summary['failed']} failed",
            'metadata' => $summary,
        ]);
    }

    public function create(array $data): Mikrotik
    {
        return DB::transaction(function () use ($data) {
            if (! empty($data['is_default'])) {
                Mikrotik::where('is_default', true)->update(['is_default' => false]);
            }

            $data['connected'] = false;

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

            ActivityLog::create([
                'user_id' => auth()->id(),
                'action' => 'mikrotik_created',
                'resource_type' => Mikrotik::class,
                'resource_id' => $mikrotik->id,
                'description' => 'Mikrotik server created: '.$mikrotik->name,
            ]);

            TestMikrotikConnectionJob::dispatch($mikrotik->id)->afterCommit();

            return $mikrotik;
        });
    }

    public function testConnection(array $data): array
    {
        return $this->apiService->testConnection($data);
    }

    public function update(Mikrotik $mikrotik, array $data): bool
    {
        return DB::transaction(function () use ($mikrotik, $data) {
            if (! empty($data['is_default'])) {
                Mikrotik::where('is_default', true)->where('id', '<>', $mikrotik->id)->update(['is_default' => false]);
            }

            $result = $this->repository->update($mikrotik, $data);

            ActivityLog::create([
                'user_id' => auth()->id(),
                'action' => 'mikrotik_updated',
                'resource_type' => Mikrotik::class,
                'resource_id' => $mikrotik->id,
                'description' => 'Mikrotik server updated: '.$mikrotik->name,
            ]);

            TestMikrotikConnectionJob::dispatch($mikrotik->id)->afterCommit();

            return $result;
        });
    }

    public function delete(Mikrotik $mikrotik): bool
    {
        $result = $this->repository->delete($mikrotik);

        ActivityLog::create([
            'user_id' => auth()->id(),
            'action' => 'mikrotik_deleted',
            'resource_type' => Mikrotik::class,
            'resource_id' => $mikrotik->id,
            'description' => 'Mikrotik server deleted: '.$mikrotik->name,
        ]);

        return $result;
    }
}
