<?php

namespace App\Services\Payments;
use Illuminate\Support\Facades\Log;
use App\Contracts\PaymentGatewayInterface;
use Stripe\Stripe;
use Stripe\Customer;
use Stripe\PaymentMethod;
use Stripe\PaymentIntent;
use Stripe\Transfer;
use Stripe\Payout;


use Stripe\Account;
use Stripe\AccountLink;
use Stripe\SetupIntent;
use App\Models\Company;
use App\Models\PaymentProfile;

/**
 * StripeGateway
 *
 * Concrete implementation of PaymentGatewayInterface using Stripe API.
 * Handles customer creation, payment method management, charges, transfers, and payouts.
 */
class StripeGateway implements PaymentGatewayInterface
{
    /**
     * Initialize Stripe with secret key from config.
     */
    public function __construct()
    {
        Stripe::setApiKey(config('stripe.secret_key'));
        Stripe::setApiVersion('2020-08-27');
    }

    // ========= CONNECTED ACCOUNT LOGIC ==========

    /**
     * Create a Stripe Connect onboarding link for an existing PaymentProfile.
     * It will only update `provider_account_id` if not already set.
     * 
     * @param int $company_id
     * @return string Stripe onboarding link URL
     * 
     * @throws \Exception if profile or company is not found
     * 
     * Docs:
     * https://stripe.com/docs/connect/express-accounts
     */
    public function createConnectOnboardingLink(int $company_id): string
    {
        // Step 1: Fetch existing PaymentProfile (don't create)
        $profile = PaymentProfile::where('company_id', $company_id)
            ->where('provider', 'stripe')
            ->first();

        if (!$profile) {
            throw new \Exception("PaymentProfile not found for company_id: $company_id");
        }

        // Step 2: Fetch company to get email (required by Stripe)
        $company = Company::find($company_id);
        if (!$company) {
            throw new \Exception("Company not found for ID: $company_id");
        }

        // Step 3: Create Stripe Connected Account if not already
        if (!$profile->provider_account_id) {
            $account = Account::create([
                'type' => 'express',
                'country' => 'US',
                'email' => $company->email,
                'capabilities' => [
                    'transfers' => ['requested' => true],
                ],
                'business_type' => 'company',
            ]);

            $profile->update(['provider_account_id' => $account->id]);
        }

        // Step 4: Create the onboarding link
        $link = AccountLink::create([
            'account'     => $profile->provider_account_id,
            'refresh_url' => config('stripe.frontend.refresh_url'),
            'return_url'  => config('stripe.frontend.return_url'),
            'type'        => 'account_onboarding',
        ]);

        return $link->url;
    }


    /**
     * Check if a connected account is fully verified
     * Docs: https://docs.stripe.com/connect/platform-controls-verification
     */
    public function verifyConnectAccount(string $accountId): array
    {
        $account = Account::retrieve($accountId);

        return [
            'charges_enabled' => $account->charges_enabled,
            'payouts_enabled' => $account->payouts_enabled,
            'details_submitted' => $account->details_submitted,
            'requirements_due' => $account->requirements['currently_due'] ?? [],
            'disabled_reason' => $account->disabled_reason ?? null,
        ];
    }

    /**
     * Transfer funds from platform to connected account
     * Docs: https://docs.stripe.com/connect/send-payments
     */
    public function transferToConnectedAccount(array $data): array
    {
        $transfer = \Stripe\Transfer::create([
            'amount'      => $data['amount'],
            'currency'    => $data['currency'],
            'destination' => $data['destination'],
            'description' => $data['description'] ?? null,
            'metadata'    => $data['metadata'] ?? [],
        ]);

        return $transfer->toArray();
    }

    /**
     * Send a payout to a connected account’s external bank account.
     *
     * @param string $accountId
     * @param int $amount
     * @return void
     */
    public function createPayout(string $accountId, int $amount): void
    {
        Payout::create([
            'amount' => $amount,
            'currency' => 'usd',
        ], [
            'stripe_account' => $accountId,
        ]);
    }

    /**
     * Generate a one-time Express Dashboard login link for a connected account.
     *
     * This link allows the connected user to access their Stripe Express dashboard.
     * Docs: https://stripe.com/docs/api/login_links/create
     *
     * @param string $accountId Stripe connected account ID (starts with acct_)
     * @return string login URL
     * @throws \Exception if the login link cannot be created
     */
    public function createExpressDashboardLoginLink(string $accountId): string
    {
        
        $loginLink = \Stripe\Account::createLoginLink($accountId);

        return $loginLink->url;
    }

    // ========== EXISTING REQUIRED METHODS ==========
    /**
     * Create a new customer in Stripe.
     *
     * @param array $data Customer data (name, email, metadata)
     * @return string Stripe customer ID
     */
    public function createCustomer(array $data): string
    {
        $customer = Customer::create([
            'name' => $data['name'] ?? null,
            'email' => $data['email'] ?? null,
            'metadata' => $data['metadata'] ?? [],
        ]);

        return $customer->id;
    }

    /**
     * Get customer details as array.
     *
     * @param string $customerId
     * @return array
     */
    public function retrieveCustomer(string $customerId): array
    {
        return Customer::retrieve($customerId)->toArray();
    }

    /**
     * Attach a payment method to a customer.
     * Only attaches if not already attached.
     *
     * @param string $customerId
     * @param string $paymentMethodId
     * @return void
     */
    public function attachPaymentMethod(string $customerId, string $paymentMethodId): void
    {
        $paymentMethod = PaymentMethod::retrieve($paymentMethodId);

        // Attach only if it's not already attached to the customer
        if ($paymentMethod->customer !== $customerId) {
            $paymentMethod->attach(['customer' => $customerId]);
        }
    }

    /**
     * Set a customer's default payment method.
     * Skips update if the new method is functionally the same as the current one (by fingerprint).
     *
     * @param string $customerId
     * @param string $newPaymentMethodId
     * @return string 'updated' or 'already-default'
     */
    public function changePaymentMethod(string $customerId, string $newPaymentMethodId): bool
    {

            // Retrieve the submitted payment method from Stripe
            $paymentMethod = PaymentMethod::retrieve($newPaymentMethodId);

            // Retrieve the Stripe customer object
            $customer = Customer::retrieve($customerId);

            // Get current default payment method ID (if any)
            $defaultMethodId = $customer->invoice_settings->default_payment_method;

            $isSameCard = false; // Assume it's a new card by default

            if ($defaultMethodId) {
                // If a default exists, retrieve the full payment method object
                $defaultMethod = PaymentMethod::retrieve($defaultMethodId);

                // Compare fingerprints to check if same card is being re-submitted
                if (
                    isset($defaultMethod->card->fingerprint) &&
                    isset($paymentMethod->card->fingerprint)
                ) {
                    $isSameCard = $defaultMethod->card->fingerprint === $paymentMethod->card->fingerprint;
                }
            }

            // Only update if it's not the same card
            if (!$isSameCard) {
                // Attach the new payment method if it's not already attached to the customer
                if ($paymentMethod->customer !== $customerId) {
                    $paymentMethod->attach([
                        'customer' => $customerId,
                    ]);
                }

                // Set the new payment method as default for the customer
                Customer::update($customerId, [
                    'invoice_settings' => [
                        'default_payment_method' => $newPaymentMethodId,
                    ],
                ]);
            }

            if($isSameCard){
                return true;
            }

            return false;
    }


    /**
     * Create a PaymentIntent to charge a customer.
     *
     * @param array $data
     * @return array
     */
    public function createPaymentIntent(array $data): array
    {
        if (empty($data['customer_id'])) {
            throw new \InvalidArgumentException('Stripe customer_id is required.');
        }

        if (!isset($data['amount']) || !is_numeric($data['amount']) || $data['amount'] <= 0) {
            throw new \InvalidArgumentException('Amount must be a positive number.');
        }


            // Step 1: Retrieve customer info from Stripe
            $customer = \Stripe\Customer::retrieve($data['customer_id']);
            $defaultPaymentMethodId = $customer->invoice_settings->default_payment_method;

            // Step 2: Build the PaymentIntent payload
            $intentData = [
                'amount' => $data['amount'],
                'currency' => $data['currency'],
                'customer' => $data['customer_id'],
                'confirm' => true,
                'off_session' => true,
                'description' => $data['description'] ?? null,
                'metadata' => $data['metadata'] ?? [],
            ];
            
            // Step 3: Add payment method if explicitly provided or fallback to default
            if (!empty($data['payment_method_id'])) {
                $intentData['payment_method'] = $data['payment_method_id'];
            } elseif (!empty($defaultPaymentMethodId)) {
                $intentData['payment_method'] = $defaultPaymentMethodId;
            } else {
                throw new \Exception('No payment method provided and no default found.');
            }

            // Step 4: Create the PaymentIntent
            $intent = PaymentIntent::create($intentData);
            Log::info('Stripe PaymentIntent created', $intent->toArray());
            return $intent->toArray();


    }

    /**
     * Fetch raw details of a payment method.
     *
     * @param string $paymentMethodId
     * @return array
     */
    public function getRawPaymentMethod(string $paymentMethodId): array
    {
        return PaymentMethod::retrieve($paymentMethodId)->toArray();
    }

    public function createSetupIntent(string $customerId): array
    {
        $intent = \Stripe\SetupIntent::create([
            'customer' => $customerId,
            'usage' => 'off_session',
        ]);

        return $intent->toArray();
    }

}