<?php

namespace Database\Factories;

use App\Models\Prefixes;
use App\Models\Organisation;
use App\Models\Asns;
use App\Models\Goliath\GoliathAppliance;
use App\Models\Goliath\GoliathAttack;
use App\Models\Goliath\GoliathAttackCollected;
use App\Models\Goliath\GoliathAttackPrefix;
use App\Models\Goliath\GoliathRuleset;
use App\Models\Goliath\PrefixGoliathFlag;
use App\Models\Goliath\PrefixGoliathFirewall;
use App\Models\Goliath\PrefixGoliathInline;
use App\Models\Goliath\PrefixGoliathLoose;
use App\Models\PrefixCommunity;
use App\Models\PrefixSubtenants;
use App\Models\Supernets;
use Illuminate\Database\Eloquent\Factories\Factory;
use Carbon\Carbon;

class PrefixesFactory extends Factory
{
    protected $model = Prefixes::class;

    /**
     * Define the model's default state.
     */
    public function definition(): array
    {
        // Generate random IPv4 address and mask
        $mask = $this->faker->numberBetween(8, 32);
        $address = $this->generateValidIPv4ForMask($mask);

        return [
            'name' => $this->faker->optional()->company() . ' Network',
            'address' => $address,
            'mask' => $mask,
            'seen_at' => Carbon::now(),
            'still_announced' => true,
            'created_source' => 'prefix-list-uncrushed',
            'organisation_id' => null,  // Must be defined every usage.
            'asn_id' => null,           // Must be defined every usage.
            'setup_required' => 0
        ];
    }

    /**
     * Generate a valid IPv4 address for a given mask
     */

    private function generateValidIPv4ForMask(int $mask): string
    {
        // For smaller masks (larger networks), use more realistic network addresses
        if ($mask <= 16) {
            // Generate Class A or B style networks
            $first = $this->faker->numberBetween(10, 172);
            $second = $this->faker->numberBetween(0, 255);
            return "$first.$second.0.0";
        } elseif ($mask <= 24) {
            // Generate Class C style networks
            $first = $this->faker->numberBetween(192, 223);
            $second = $this->faker->numberBetween(0, 255);
            $third = $this->faker->numberBetween(0, 255);
            return "$first.$second.$third.0";
        } else {
            // For host addresses or small subnets
            return $this->faker->ipv4();
        }
    }

    
    /**
     * State for announced prefixes
     */
    public function name(string $name): static
    {
        return $this->state(function (array $attributes) use($name) {
            return [
                'name' => $name,
            ];
        });
    }

    /**
     * Configure the factory to use existing organisation
     */
    public function ownership(Organisation $organisation, Asns $asn): static
    {
        return $this->state(function (array $attributes) use ($organisation, $asn) {
            return [
                'organisation_id'   => $organisation->id,
                'asn_id'            => $asn->id
            ];
        });
    }

    /**
     * State for announced prefixes
     */
    public function announced(): static
    {
        return $this->state(function (array $attributes) {
            return [
                'still_announced' => true,
                'seen_at' => Carbon::now(),
            ];
        });
    }

    /**
     * State for not announced prefixes
     */
    public function notAnnounced(): static
    {
        return $this->state(function (array $attributes) {
            return [
                'still_announced' => false,
                'seen_at' => Carbon::now()->subMinute(),
            ];
        });
    }

    /**
     * State for prefixes requiring setup
     */
    public function setupRequired(): static
    {
        return $this->state(function (array $attributes) {
            return [
                'setup_required' => 1,
            ];
        });
    }

    /**
     * State for specific network size
     */
    public function address(string $address, int $mask): static
    {
        return $this->state(function (array $attributes) use ($address, $mask) {
            return [
                'mask' => $mask,
                'address' => $address,
            ];
        });
    }

    /**
     * State for specific created source
     */
    public function source(string $source): static
    {
        return $this->state(function (array $attributes) use ($source) {
            return [
                'created_source' => $source,
            ];
        });
    }

    /**
     * Configure factory with Goliath Flag settings
     */
    public function flags(int|null $heuristics = 3, bool $synproxy = false, array|null $filters = null, bool $special = false, bool $block_udp = false, bool $block_tcp = false, bool $block_fragments = false, bool $syn_cookies = false, bool $drop_tcp_out_of_state = false, bool $app_filter_strict = false, bool $null_route = false, bool $syn_cookie_data_promote = false, string|null $syn_cookie_os_gate = null, string|null $syn_challenge = null, bool $syn_drop_first = false, int|null $src_limit_pps = null, int|null $src_limit_mbps = null, bool $drop_unexpected_protocols = false, bool $drop_non_core_protocols = false, bool $drop_udp_to_tcp_ports = false, bool $udp_established_lane = false): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($heuristics, $synproxy, $filters, $special, $block_udp, $block_tcp, $block_fragments, $syn_cookies, $drop_tcp_out_of_state, $app_filter_strict, $null_route, $syn_cookie_data_promote, $syn_cookie_os_gate, $syn_challenge, $syn_drop_first, $src_limit_pps, $src_limit_mbps, $drop_unexpected_protocols, $drop_non_core_protocols, $drop_udp_to_tcp_ports, $udp_established_lane) {
            PrefixGoliathFlag::create([
                'prefix_id' => $prefix->id,
                'flag_synproxy' => $synproxy,
                'flag_heuristics' => boolval($heuristics),
                'flag_heuristics_level' => $heuristics,
                'flag_block_udp' => $block_udp,
                'flag_block_tcp' => $block_tcp,
                'flag_block_fragments' => $block_fragments,
                'flag_syn_cookies' => $syn_cookies,
                'flag_drop_tcp_out_of_state' => $drop_tcp_out_of_state,
                'flag_app_filter_strict' => $app_filter_strict,
                'flag_null_route' => $null_route,
                'flag_syn_cookie_data_promote' => $syn_cookie_data_promote,
                'flag_syn_cookie_os_gate' => $syn_cookie_os_gate,
                'flag_syn_challenge' => $syn_challenge,
                'flag_syn_drop_first' => $syn_drop_first,
                'flag_src_limit_pps' => $src_limit_pps,
                'flag_src_limit_mbps' => $src_limit_mbps,
                'flag_drop_unexpected_protocols' => $drop_unexpected_protocols,
                'flag_drop_non_core_protocols' => $drop_non_core_protocols,
                'flag_drop_udp_to_tcp_ports' => $drop_udp_to_tcp_ports,
                'flag_udp_established_lane' => $udp_established_lane,
                'flag_filters' => $filters === null ? null : json_encode($filters),
                'flag_special' => $special,
                'region_id' => null,
                'subregion_id' => null,
                'metro_id' => null,
            ]);
        });
    }

    /**
     * Configure factory with Goliath Firewall settings
     */
    public function firewall(GoliathRuleset|null $ruleset, $region = null, $subregion = null): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($ruleset, $region, $subregion) {
            $defaultConfig = [
                'prefix_id' => $prefix->id,
                'region_id' => $region,
                'subregion_id' => $subregion,
                'metro_id' => null,
                'ruleset_id' => $ruleset === null ? null : $ruleset->id,
            ];

            PrefixGoliathFirewall::create($defaultConfig);
        });
    }

    /**
     * Configure factory with Goliath Inline settings
     */
    public function inline(bool $forced = false): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($forced) {
            PrefixGoliathInline::create([
                'prefix_id' => $prefix->id,
                'forced' => $forced,
            ]);
        });
    }

    /**
     * Configure factory with Goliath Loose settings
     */
    public function loose(bool $loose = false): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($loose) {
            PrefixGoliathLoose::create([
                'prefix_id' => $prefix->id,
                'loose' => $loose,
            ]);
        });
    }

    /**
     * Configure factory with communities
     */
    public function community(string $community, bool $large): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($community, $large) {

            PrefixCommunity::create([
                'prefix_id' => $prefix->id,
                'is_large' => $large,
                'community' => $community,
            ]);

        });
    }

    public function mercury(): static
    {
        return $this->afterCreating(function (Prefixes $prefix) {

            PrefixCommunity::create([
                'prefix_id' => $prefix->id,
                'is_large' => false,
                'community' => '7578:91',
            ]);

        });   
    }

    /**
     * Configure factory with Goliath Firewall settings
     */
    public function parent(Prefixes $parent): static
    {
        return $this->afterCreating(function (Prefixes $child) use ($parent) {

            Supernets::create([
                'supernet_id'   => $parent->id,
                'subnet_id'     => $child->id,
                'tenant_id'     => $child->organisation_id
            ]);

            Supernets::create([
                'supernet_id'   => $parent->id,
                'subnet_id'     => $child->id,
                'tenant_id'     => $parent->organisation_id
            ]);

        });
    }

    public function subtenant(Organisation $organisation, ?Carbon $age = null): static
    {
        return $this->afterCreating(function (Prefixes $prefix) use ($organisation, $age) {

            PrefixSubtenants::insert([
                [
                    'prefix_id' => $prefix->id,
                    'also_subnets' => 1,
                    'subtenant_id' => $organisation->id,
                    'created_at' => $age === null ? Carbon::now() : $age,
                    'updated_at' => $age === null ? Carbon::now() : $age
                ],
                [
                    'prefix_id' => $prefix->id,
                    'also_subnets' => 0,
                    'subtenant_id' => $organisation->id,
                    'created_at' => $age === null ? Carbon::now() : $age,
                    'updated_at' => $age === null ? Carbon::now() : $age
                ]
            ]);

        });
    }

    /**
     * Allow setting of time
     */

    public function seen(Carbon $seen): static
    {
        return $this->state(function (array $attributes) use ($seen) {
            return [
                'seen_at' => $seen,
            ];
        });
    }

    public function deleted(Carbon $deleted): static
    {
        return $this->state(function (array $attributes) use ($deleted) {
            return [
                'deleted_at' => $deleted,
            ];
        });
    }

}