<?php

namespace Tests\Feature;

use App\Console\Commands\Billing\GenerateInvoices;
use App\Models\Billing\BillingCommit;
use App\Models\Billing\Invoice;
use App\Models\Group;
use App\Models\Organisation;
use App\Models\User;
use App\Notifications\SlackNotification;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class BillingOverageTest extends TestCase
{

    use RefreshDatabase;

    private $organisation = null;
    private $group = null;
    private $user = null;

    protected function setUp(): void
    {
        parent::setUp();

        /**
         * Make sure environment is safe
         */
        $endpoint_billing = env('ENDPOINT_BILLING_MICROSERVICE_URL') . '?key=' . env('ENDPOINT_BILLING_MICROSERVICE_SECRET');
        $endpoint_overage_invoice = env('ENDPOINT_OVERAGE_MICROSERVICE_URL');

        Http::fake([
            $endpoint_billing => Http::response([
                'billing_ids' => [
                    [ 'id' => 1, 'client_id' => 1, 'name' => 'IPT Test 1', 'domain' => '' ],
                    [ 'id' => 2, 'client_id' => 1, 'name' => 'IPT Test 2', 'domain' => '' ],
                    [ 'id' => 3, 'client_id' => 1, 'name' => 'IPT Test 3', 'domain' => '' ],
                    [ 'id' => 4, 'client_id' => 2, 'name' => 'IPT Test', 'domain' => '' ],
                    [ 'id' => 5, 'client_id' => 3, 'name' => 'IPT Test', 'domain' => '' ],
                ],
                'customer_ids' => [
                    [ 'id' => 1, 'name' => 'Client 1' ],
                    [ 'id' => 2, 'name' => 'Client 2' ],
                    [ 'id' => 3, 'name' => 'Client 3' ]
                ],
                'service_ids' => []
            ], 200),
            "$endpoint_overage_invoice/invoice" => Http::response([ 'Success' ], 200),
            '*' => Http::response([ 'Fallback Error' ], 500) // Fallback
        ]);

        Notification::fake();

        /**
         * Database setup and preamble
         */

        $this->organisation = Organisation::create([ 'name' => '' ]);
        $this->group        = Group::create([ 'name' => 'Goliath' ]);
        $this->user         = User::create([
            'name'              => 'Test McTest',
            'email'             => 'creatia@globalsecurelayer.com',
            'phone'             => null,
            'password'          => '',
            'group_id'          => $this->group->id,
            'organisation_id'   => $this->organisation->id
        ]);
    }

    public function testOptions()
    {

        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/option');

        $this->assertEquals(200, $response->status(), 'Expected a 200 response was received when fetching options for overages.');

        $this->assertEquals(3, sizeof($response->json()['brands']), 'Something is wrong with the overage options response.');

    }

    public function testAdd()
    {
        /**
         * Test what should be valid assertions
         */
        $overages = [
            [
                'effective_at' => Carbon::parse('2024-12-01'),
                'billing_day' => 1,
                'commit' => 1000,
                'price_per_unit' => 0.5,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 1,
                'whmcs_service_id' => 1
            ],
            [
                'effective_at' => Carbon::parse('2025-01-01'),
                'billing_day' => 2,
                'commit' => 1250,
                'price_per_unit' => 0.4,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 2,
                'whmcs_service_id' => 4
            ],
            [
                'effective_at' => Carbon::parse('2025-02-01'),
                'billing_day' => 3,
                'commit' => 1500,
                'price_per_unit' => 0.3,
                'currency' => 'USD',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 3,
                'whmcs_service_id' => 5
            ]
        ];

        foreach($overages as $o) {
            $response = $this->actingAs($this->user)->withoutMiddleware()->post('/api/admin/billing/overage', array_merge($o, [ 'method' => 'global' ]));

            $this->assertEquals(200, $response->status(), 'Expected a 200 response when adding a commit. Input was: ' . json_encode($o));
        }

        /**
         * Test ones that should fail
         */
        $overages = [
            [
                'effective_at' => Carbon::parse('2024-01-08'),
                'billing_day' => 1,
                'commit' => 1000,
                'price_per_unit' => 0.5,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 3,
                'whmcs_service_id' => 5
            ]
        ];

        foreach($overages as $o) {
            $response = $this->actingAs($this->user)->withoutMiddleware()->post('/api/admin/billing/option', $o);

            $this->assertNotEquals(200, $response->status(), 'Received a 200 response when adding an invalid commit. Input was: ' . json_encode($o));
        }

    }

    public function testList()
    {

        $this->testAdd();

        $base_search = [
            'draw' => 1,
            'start' => 0,
            'length' => 100,
        ];

        // Select everything
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query($base_search));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] != 0, 'The number of overages returned should not be zero.');

        // Perform a global search
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'global' => '2025'
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 2, 'Only two should be returned.');

        // Search by effective at
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'column' => [
                    'effective_at' => '2024'
                ]
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 1, 'Only one should be returned.');

        // Search by billing day
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'column' => [
                    'billing_day' => '1'
                ]
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 1, 'Only one should be returned.');

        // Search by ppu
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'column' => [
                    'price_per_unit' => '.4'
                ]
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 1, 'Only one should be returned.');

        // Search by brand label
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'column' => [
                    'search_brand' => 'gs'
                ]
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 3, 'Only three should be returned.');

        // Search by company label
        $response = $this->actingAs($this->user)->withoutMiddleware()->get('/api/admin/billing/overage?' . http_build_query([
            ...$base_search,
            'filter' => [
                'column' => [
                    'search_name' => 'Cli'
                ]
            ]
        ]));

        $this->assertEquals(200, $response->status(), 'Expected a 200 response to be received when fetching overages.');

        $this->assertTrue($response->json()['recordsTotal'] == 3, 'Only three should be returned.');

    }

    public function testCalculatePeriod()
    {
        $command = new GenerateInvoices;

        /**
         * Test all valid potentially complex scenarios
         */
        $tests = [
            [
                'now'       => '2025-01-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 1 ]),
                'result'    => [ '2024-12-01 00:00:00', '2024-12-31 23:59:59' ]
            ],
            [
                'now'       => '2025-02-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 1 ]),
                'result'    => [ '2025-01-01 00:00:00', '2025-01-31 23:59:59' ]
            ],
            [
                'now'       => '2025-01-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 2 ]),
                'result'    => [ '2024-12-02 00:00:00', '2025-01-01 23:59:59' ]
            ],
            [
                'now'       => '2025-02-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 2 ]),
                'result'    => [ '2025-01-02 00:00:00', '2025-02-01 23:59:59' ]
            ],
            [
                'now'       => '2025-03-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 1 ]),
                'result'    => [ '2025-02-01 00:00:00', '2025-02-28 23:59:59' ]
            ],
            [
                'now'       => '2025-02-01 00:00:00',
                'commit'    => new BillingCommit([ 'billing_day' => 28 ]),
                'result'    => [ '2025-01-28 00:00:00', '2025-02-27 23:59:59' ]
            ]
        ];

        foreach($tests as $test) {

            $now = Carbon::parse($test['now']);
            $previous_month = Carbon::parse($test['now'])->subMonth();

            $result = $command->calculatePeriod($previous_month, $now, $test['commit']);

            $this->assertTrue($test['result'][0] == $result[0]->format('Y-m-d H:i:s') && $test['result'][1] == $result[1]->format('Y-m-d H:i:s'), 'Failed a test condition for time calculation. Case:' . json_encode($test) . ', Outcome:' . json_encode($result));

        }

    }

    public function testGenerateInvoices()
    {

        /**
         * Generate our overages after deleting any prior ones
         */

        $commits = DB::table('billing_commits')->delete();

        $overages = [
            // Client 1 service 1, 2 and 3
            [
                'effective_at' => Carbon::parse('2024-12-01'),
                'billing_day' => 1,
                'commit' => 1000,
                'price_per_unit' => 0.1,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 1,
                'whmcs_service_id' => 1
            ],
            [
                'effective_at' => Carbon::parse('2024-12-01'),
                'billing_day' => 1,
                'commit' => 1100,
                'price_per_unit' => 0.2,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 1,
                'whmcs_service_id' => 2
            ],
            [
                'effective_at' => Carbon::parse('2024-12-01'),
                'billing_day' => 1,
                'commit' => 1200,
                'price_per_unit' => 0.3,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 1,
                'whmcs_service_id' => 3
            ],

            // Client 1 service 3 this overage should override the other
            [
                'effective_at' => Carbon::parse('2024-12-02'),
                'billing_day' => 1,
                'commit' => 1200,
                'price_per_unit' => 0.3,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 1,
                'whmcs_service_id' => 3
            ],

            // Client 2 service 4
            [
                'effective_at' => Carbon::parse('2025-01-01'),
                'billing_day' => 6,
                'commit' => 1250,
                'price_per_unit' => 0.4,
                'currency' => 'GBP',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 2,
                'whmcs_service_id' => 4
            ],

            // Client 3 service 5
            [
                'effective_at' => Carbon::parse('2025-02-01'),
                'billing_day' => 16,
                'commit' => 1500,
                'price_per_unit' => 0.3,
                'currency' => 'USD',
                'whmcs_brand' => 'gsl',
                'whmcs_client_id' => 3,
                'whmcs_service_id' => 5
            ]
        ];

        foreach($overages as $o) {
            $response = $this->actingAs($this->user)->withoutMiddleware()->post('/api/admin/billing/overage', array_merge($o, [ 'method' => 'global' ]));

            $this->assertEquals(200, $response->status(), 'Expected a 200 response when adding a commit. Input was: ' . json_encode($o));
        }

        /**
         * Do our invoice generation runs
         */

        // Run 1 - Day 1 - Should be 3 notifications and three invoices made

        Carbon::setTestNow('2025-02-01 03:00:00');

        $this->artisan('billing:generate-invoices', []);

        $this->assertCount(3, Invoice::all(), 'The correct amount of invoices for Run 1 were not generated.');

        // Run 2 - Day 2 - Should be 0 notifications and three invoices total

        Notification::fake();
        Carbon::setTestNow('2025-02-02 03:00:00');

        $this->artisan('billing:generate-invoices', []);

        Notification::assertNothingSent();

        $this->assertCount(3, Invoice::all(), 'The correct amount of invoices for Run 1 were not generated.');

        // Run 3 - Day 6 - Should be one notification and a total of four invoices at this point

        Notification::fake();
        Carbon::setTestNow('2025-02-06 03:00:00');

        $this->artisan('billing:generate-invoices', []);

        $this->assertCount(4, Invoice::all(), 'The correct amount of invoices for Run 3 were not generated.');

        // Run 4 - Day 16 - Should be one notification and a total of five invoices
        
        Notification::fake();
        Carbon::setTestNow('2025-02-16 03:00:00');

        $this->artisan('billing:generate-invoices', []);

        $this->assertCount(5, Invoice::all(), 'The correct amount of invoices for Run 4 were not generated.');

        // Run 5 - Day 1 - Reattempt the first day - Nothing should change

        Notification::fake();
        Carbon::setTestNow('2025-02-01 03:00:00');

        $this->artisan('billing:generate-invoices', []);

        Notification::assertNothingSent();

        $this->assertCount(5, Invoice::all(), 'The correct amount of invoices for Run 5 were not generated.');        
    }


}
