<?php

namespace App\Http\Controllers\Base;

use SoapClient;
use App\Helpers\Helper;
use App\Models\Base\Pac;
use App\Models\Base\Folio;
use Illuminate\Support\Str;
use App\Models\Base\Company;
use Illuminate\Http\Request;
use App\Models\Catalogs\Bank;
use App\Models\Catalogs\Country;
use App\Models\Base\BranchOffice;
use App\Models\Base\DocumentType;
use App\Models\Catalogs\Currency;
use App\Models\System\SysCustomer;
use App\Models\Catalogs\TaxRegimen;
use App\Http\Controllers\Controller;
use App\Models\System\SysSalesOrder;
use Illuminate\Support\Facades\Crypt;
use Symfony\Component\Process\Process;
use Illuminate\Validation\ValidationException;
use Symfony\Component\Process\Exception\ProcessFailedException;

class InstallationController extends Controller
{
    public function formStep1()
    {
        if(setting('app_installation_step','0') == 5){
            //Redireccion
            return redirect('home');
        }

        $company = Helper::defaultCompany();

        $tax_regimens = TaxRegimen::populateSelect()->get()->pluck('name_sat', 'id');
        $countries = Country::populateSelect()->get()->pluck('name_sat', 'id');
        $banks = Bank::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');

        return view('base.installations.step_1',
            compact('company', 'tax_regimens', 'countries', 'banks', 'currencies'));
    }

    public function step1(Request $request, Company $company)
    {
        //Validacion
        $request->merge(['name' => trim(str_replace('  ', ' ', $request->name))]);
        $request->merge(['taxid' => strtoupper(trim($request->taxid))]);

        $this->validate($request, [
            'name' => 'required|string',
            'file_image' => 'nullable|mimes:jpeg,jpg,png|max:2048',
            'taxid' => [
                'required',
                'regex:/^[A-Z&Ñ]{3,4}[0-9]{2}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])[A-Z0-9]{2}[0-9A]$/i'
            ],
            'tax_regimen_id' => 'required|integer',
            'email' => 'nullable|email',
            'country_id' => 'required|integer',
            'postcode' => 'required',
        ], [
            'name.*' => __('base/company.error_name'),
            'file_image.*' => __('base/company.error_image'),
            'taxid.required' => __('base/company.error_taxid'),
            'taxid.regex' => __('base/company.error_taxid_format'),
            'tax_regimen_id.*' => __('base/company.error_tax_regimen_id'),
            'email.email' => __('base/company.error_email_format'),
            'country_id.*' => __('base/company.error_country_id'),
            'postcode.*' => __('base/company.error_postcode'),
        ]);

        //Validaciones manuales
        $validator = \Validator::make([], []);

        //Valida que no exista un directorio con el mismo nombre
        if($company->taxid != $request->taxid && \Storage::exists($request->taxid)) {
            $validator->after(function ($validator) {
                $validator->errors()->add('taxid', __('base/company.error_taxid_directory_exists'));
            });
        }
        if ($validator->fails()) {
            throw new ValidationException($validator);
        }

        \DB::connection('tenant')->beginTransaction();
        try {
            //Rfc para validar cambio de directorio
            $taxid_old = $company->taxid;

            //Logica
            $request->merge(['updated_uid' => \Auth::user()->id]);
            $company->fill($request->only([
                'updated_uid',
                'name',
                'image',
                'taxid',
                'tax_regimen_id',
                'email',
                'phone',
                'address_1',
                'address_2',
                'address_3',
                'address_4',
                'city_id',
                'state_id',
                'country_id',
                'postcode',
            ]));

            //Si suben una imagen
            if ($request->hasFile('file_image')) {
                //Si ya tenia un archivo lo eliminamos
                if (!empty($company->image)) {
                    \Storage::delete(Helper::setDirectory(Company::PATH_IMAGES,$company->id) . '/' . $company->image);
                }
                $image = Helper::uploadFileImage('file_image', Company::PATH_IMAGES,$company->id);
                $company->image = $image;
            } else {
                $company->image = $request->image; //si no tiene archivo sobreescribimos elque tenia
            }

            //Guardar
            //Registro principal
            $company->save();

            //Crea un cliente en automatico para finkok de timbrado bajo demanda
            if($taxid_old != $company->taxid){
                $pac = Pac::findOrFail(setting('default_pac_id')); //PAC
                if(!empty($pac)){
                    if(preg_match('/finkok/i', $pac->code)) {
                        $client = new SoapClient(str_replace('cancel', 'registration', $pac->ws_url_cancel));
                        $params = [
                            'reseller_username' => $pac->username,
                            'reseller_password' => Crypt::decryptString($pac->password),
                            'taxpayer_id' => $company->taxid,
                            'type_user' => 'O',
                        ];
                        $response = $client->__soapCall('add', ['parameters' => $params]);
                    }
                }
            }

            //Si el RFC cambia renombramos
            if (!empty($taxid_old) && \Storage::exists($taxid_old) && $taxid_old != $company->taxid) {
                \Storage::move($taxid_old, $company->taxid);
            }

            //Actualiza estatus de instalacion
            setting()->set('app_installation_step', '2');
            setting()->save();

            \DB::connection('tenant')->commit();

            //Redireccion
            return redirect('base/installation/step-2');
        } catch (\Exception $e) {
            \DB::connection('tenant')->rollback();
            flash($e->getMessage())->error();
            return back()->withInput();
        }
    }

    public function formStep2()
    {
        if(setting('app_installation_step','0') == 5){
            //Redireccion
            return redirect('home');
        }

        $company = Helper::defaultCompany();

        return view('base.installations.step_2',
            compact('company'));
    }

    public function skipStep2()
    {
        if(setting('app_installation_step','0') == 5){
            //Redireccion
            return redirect('home');
        }

        //Actualiza estatus de instalacion
        setting()->set('app_installation_step', '3');
        setting()->save();

        //Redireccion
        return redirect('base/installation/step-3');
    }

    public function step2(Request $request, Company $company)
    {
        //Validacion
        if ($request->hasFile('file_file_cer')) {
            $request->merge(['file_file_cer_ext' => strtolower(request()->file('file_file_cer')->getClientOriginalExtension())]);
        }
        if ($request->hasFile('file_file_key')) {
            $request->merge(['file_file_key_ext' => strtolower(request()->file('file_file_key')->getClientOriginalExtension())]);
        }
        $this->validate($request, [
            'file_file_cer_ext' => 'required|in:cer',
            'file_file_key_ext' => 'required|in:key',
            'password_key' => 'required',
        ], [
            'file_file_cer_ext.*' => __('base/company.error_file_cer'),
            'file_file_key_ext.*' => __('base/company.error_file_key'),
            'password_key.*' => __('base/company.error_password_key'),
        ]);

        \DB::connection('tenant')->beginTransaction();
        try {
            //Rfc para validar cambio de directorio
            $taxid_old = $company->taxid;

            //Logica
            $request->merge(['updated_uid' => \Auth::user()->id]);
            $company->fill($request->only([
                'updated_uid',
            ]));

            //Archivos SAT
            //Convertir en CER a PEM
            $path_file_cer_pem = '';
            if ($request->hasFile('file_file_cer')) {
                $tmp = $this->convertCerToPem($company); //
                $company->file_cer = !empty($tmp['file_cer']) ? $tmp['file_cer'] : null;
                $company->certificate_number = !empty($tmp['certificate_number']) ? $tmp['certificate_number'] : null;
                $company->date_start = !empty($tmp['date_start']) ? $tmp['date_start'] : null;
                $company->date_end = !empty($tmp['date_end']) ? $tmp['date_end'] : null;
                $company->titular = !empty($tmp['titular']) ? $tmp['titular'] : null;
                $path_file_cer_pem = !empty($tmp['path_file_cer_pem']) ? $tmp['path_file_cer_pem'] : null;
            } else {
                $company->file_cer = $request->file_cer;
            }
            //Convertir en KEY a PEM, debe contener la contraseña
            $path_file_key_pem = '';
            if ($request->hasFile('file_file_key')) {
                $tmp = $this->convertKeyToPem($company,$request->password_key, $path_file_cer_pem); //
                $company->password_key = !empty($tmp['password_key']) ? $tmp['password_key'] : null;
                $company->file_key = !empty($tmp['file_key']) ? $tmp['file_key'] : null;
                $path_file_key_pem = !empty($tmp['path_file_key_pem']) ? $tmp['path_file_key_pem'] : null;
            } else {
                $company->file_key = $request->file_key;
            }
            //Crear archivo PFX
            if (!empty($path_file_cer_pem) && !empty($path_file_key_pem)) {
                $tmp = $this->createPfx($company,$path_file_key_pem, $request->password_key, $path_file_cer_pem); //
                $company->file_pfx = !empty($tmp['file_pfx']) ? $tmp['file_pfx'] : null;
            } else {
                $company->file_pfx = $request->file_pfx;
            }

            //Guardar
            //Registro principal
            $company->save();


            //Actualiza estatus de instalacion
            setting()->set('app_installation_step', '3');
            setting()->save();

            \DB::connection('tenant')->commit();

            //Redireccion
            return redirect('base/installation/step-3');
        } catch (\Exception $e) {
            $msg = $e->getMessage();
            if(strstr( $msg, 'rror decrypting ke')){
                $msg = __('general.error_password_file_key');
            }
            \DB::connection('tenant')->rollback();
            flash($msg)->error();
            return back()->withInput();
        }
    }

    public function formStep3()
    {
        if(setting('app_installation_step','0') == 5){
            //Redireccion
            return redirect('home');
        }

        $company = Helper::defaultCompany();
        $branch_office = BranchOffice::active()->get()->first();
        $countries = Country::populateSelect()->get()->pluck('name_sat', 'id');

        return view('base.installations.step_3',
            compact('branch_office', 'countries','company'));
    }

    public function step3(Request $request, BranchOffice $branch_office)
    {
        //Validacion
        $this->validate($request, [
            'name' => 'required|string',
            'country_id' => 'required|integer',
            'postcode' => 'required',
        ], [
            'name.*' => __('base/branch_office.error_name'),
            'country_id.*' => __('base/branch_office.error_country_id'),
            'postcode.*' => __('base/branch_office.error_postcode'),
        ]);

        //Logica
        $request->merge(['updated_uid' => \Auth::user()->id]);
        $branch_office->fill($request->only([
            'updated_uid',
            'name',
            'address_1',
            'address_2',
            'address_3',
            'address_4',
            'city_id',
            'state_id',
            'country_id',
            'postcode',
        ]));

        //Guardar
        //Registro principal
        $branch_office->save();

        //Actualiza estatus de instalacion
        setting()->set('app_installation_step', '4');
        setting()->save();

        //Redireccion
        return redirect('base/installation/step-4');
    }

    public function formStep4(Request $request)
    {
        if(setting('app_installation_step','0') == 5){
            //Redireccion
            return redirect('home');
        }

        $limit = ($request->has('limit') ? $request->get('limit') : 100);
        $results = DocumentType::filter([])->sortable('name')->whereNull('branch_office_id')->paginate($limit);

        return view('base.installations.step_4',
            compact('results'));
    }

    public function step4(Request $request)
    {
        //Validacion
        $this->validate($request, [

            'item.*.code' => 'required',
            'item.*.name' => 'required',
            'item.*.prefix' => 'required',
            'item.*.current_number' => 'required|numeric|min:0',
        ], [
            'item.*.code.*' => __('base/document_type.error_code'),
            'item.*.name.*' => __('base/document_type.error_name'),
            'item.*.prefix.*' => __('base/document_type.error_prefix'),
            'item.*.current_number.*' => __('base/document_type.error_next_number'),
        ]);

        //Logica
        $company = Helper::defaultCompany();
        $currency = Currency::where('code','=','MXN')->get()->first();

        //Guardar
        //Registro principal
        //Lineas
        if (!empty($request->item)) {
            foreach ($request->item as $key => $item) {
                $document_type = DocumentType::where('code','=',$item['code'])->first();
                $document_type->name = $item['name'];
                $document_type->prefix = $item['prefix'];
                $document_type->current_number = (int)$item['current_number'] - 1 ;
                $document_type->save();
            }
        }

        //Crea sus primeros folios de regalo
        if((int)config('app.account_folios_free',0) > 0 && setting('app_installation_step','0') < 5) {
            $product = 'Folios de regalo';
            $quantity_folio = (int)config('app.account_folios_free', 0);
            $sys_customer = \Auth::user()->sysCustomer;
            $available_folio = 0;
            $is_distributor = false;
            if(!empty($sys_customer->distributor)) {
                $distributor = $sys_customer->distributor;
                $available_folio = $distributor->available_folio;
                $is_distributor = false;
            }

            if($quantity_folio > 0 && (!$is_distributor || ($is_distributor && $available_folio >= $request->quantity_folio))){
                $folio = Folio::create([
                    'created_uid' => \Auth::user()->id,
                    'updated_uid' => \Auth::user()->id,
                    'name' => 'XXX' . random_int(1000, 9999),
                    'product' => $product,
                    'currency_id' => !empty($currency) ? $currency->id : null,
                    'currency_value' => !empty($currency) ? $currency->rate : null,
                    'quantity' => 1,
                    'price_unit' => 1,
                    'price_reduce' => 1,
                    'amount_untaxed' => 1,
                    'amount_total' => 1,
                    'quantity_folio' => $quantity_folio,
                    'available_folio' => $quantity_folio,
                    'expiry_date' => \Date::now()->addDays(30), //Validos solo por 30 dias
                    'status' => Folio::ACTIVE
                ]);
                $folio->name = 'CFL' . $folio->id; //Actualizamos el folio
                $folio->save();

                //Guardamos en pedidos del sistema
                if(!empty($sys_customer)){
                    $sys_sales_order = SysSalesOrder::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'name' => 'XXX' . random_int(1000, 9999),
                        'sys_customer_id' => !empty($sys_customer) ? $sys_customer->id : null,
                        'currency_id' => $folio->currency_id,
                        'currency_value' => $folio->currency_value,
                        'product' => $product,
                        'quantity' => $folio->quantity,
                        'price_unit' => $folio->price_unit,
                        'price_reduce' => $folio->price_reduce,
                        'amount_untaxed' => $folio->amount_untaxed,
                        'amount_total' => $folio->amount_total,
                        'folio_id' => $folio->id,
                        'sys_customer_folio' => $folio->name,
                        'quantity_folio' => $folio->quantity_folio,
                        'activation_date' => \Date::now(),
                        'status' => SysSalesOrder::ACTIVE
                    ]);
                    $sys_sales_order->name = 'PV' . $sys_sales_order->id; //Actualizamos el folio
                    $sys_sales_order->save();
                }

                //Descuenta de los folios del distribuidor
                if(!empty($sys_customer->distributor)) {
                    $distributor = $sys_customer->distributor;
                    $distributor->available_folio -= $folio->quantity_folio;
                    $distributor->save();
                }
            }
        }

        //Actualiza estatus de instalacion
        setting()->set('app_installation_step', '5');
        setting()->save();

        //Mensaje
        flash(__('general.text_form_success_add_installation'))->success();

        //Redireccion
        return redirect('home');
    }

    /**
     * Funcion para convertir cer a pem y obtener la informacion
     *
     * @return array
     */
    private function convertCerToPem($company)
    {
        $data = [];
        $name_file_cer = Str::random(40) . '.' . request()->file('file_file_cer')->getClientOriginalExtension();
        $path_file_cer = request()->file('file_file_cer')->storeAs(Helper::setDirectory(Company::PATH_FILES,$company->id),
            $name_file_cer); //ruta temporal del archivo
        if (\Storage::exists($path_file_cer)) {
            //Convertir en PEM
            $path_file_cer_pem = $path_file_cer . '.pem';

            if(config('app.shared_hosting')){
                system('openssl x509 -inform DER -in "' . \Storage::path($path_file_cer) . '" -outform PEM -pubkey -out "' . \Storage::path($path_file_cer_pem) . '"');
            }else{
                $process = new Process('openssl x509 -inform DER -in "' . \Storage::path($path_file_cer) . '" -outform PEM -pubkey -out "' . \Storage::path($path_file_cer_pem) . '"');
                $process->run();
                if (!$process->isSuccessful()) {
                    throw new ProcessFailedException($process);
                }
            }
            //Genera informacion
            if (\Storage::exists($path_file_cer_pem)) {
                $tmp = openssl_x509_parse(\Storage::get($path_file_cer_pem));
                if($tmp['extensions']['keyUsage'] != 'Digital Signature, Non Repudiation'){
                    throw new \Exception(__('base/company.error_file_cer_validate_csd'));
                }
                $data = [
                    'file_cer' => $name_file_cer . '.pem',
                    'certificate_number' => Helper::certificateNumber($tmp['serialNumberHex']),
                    'date_start' => date('Y-m-d H:i:s', $tmp['validFrom_time_t']),
                    'date_end' => date('Y-m-d H:i:s', $tmp['validTo_time_t']),
                    'path_file_cer_pem' => $path_file_cer_pem,
                    'titular' => $tmp['subject']['O'] ?? '',
                ];
            }
            //Eliminar archivo .cer
            //\Storage::delete(Company::PATH_FILES . '/' . $name_file_cer);
        }
        return $data;
    }

    /**
     * Funcion para convertir cer a pem y obtener la informacion
     *
     * @param $request
     * @param $path_file_cer_pem
     * @return array
     * @throws \Exception
     */
    private function convertKeyToPem($company,$password_key, $path_file_cer_pem)
    {
        $data = [];
        $name_file_key = Str::random(40) . '.' . request()->file('file_file_key')->getClientOriginalExtension();
        $path_file_key = request()->file('file_file_key')->storeAs(Helper::setDirectory(Company::PATH_FILES,$company->id),
            $name_file_key);
        if (\Storage::exists($path_file_key)) {
            //
            $pass = $password_key;
            if(PHP_OS_FAMILY !== "Windows"){
                $pass = str_replace('$','\$', $pass);
            }

            //Convertir en PEM
            $path_file_key_pem = $path_file_key . '.pem';
            if(config('app.shared_hosting')){
                system('openssl pkcs8 -inform DER -in "' . \Storage::path($path_file_key) . '" -passin pass:"' . $pass . '" -outform PEM -out "' . \Storage::path($path_file_key_pem) . '"');
            }else {
                $process = new Process('openssl pkcs8 -inform DER -in "' . \Storage::path($path_file_key) . '" -passin pass:"' . $pass . '" -outform PEM -out "' . \Storage::path($path_file_key_pem) . '"');
                $process->run();
                if (!$process->isSuccessful()) {
                    throw new ProcessFailedException($process);
                }
            }
            //Convertir en PEM con contraseña
            $path_file_key_pass_pem = $path_file_key . '.pass.pem';
            if(config('app.shared_hosting')){
                system('openssl rsa -in "' . \Storage::path($path_file_key_pem) . '" -des3 -out "' . \Storage::path($path_file_key_pass_pem) . '" -passout pass:"' . $pass. '"');
            }else {
                //$process           = new Process('openssl pkcs8 -inform DER -in ' . $path_file_key . ' -passin pass:"' . $request->pass . '" -outform PEM -out ' . $path_file_key_pass_pem .' -passout pass:"' . $request->password_key.'"');
                $process = new Process('openssl rsa -in "' . \Storage::path($path_file_key_pem) . '" -des3 -out "' . \Storage::path($path_file_key_pass_pem) . '" -passout pass:"' . $pass.'"');
                $process->run();
                if (!$process->isSuccessful()) {
                    throw new ProcessFailedException($process);
                }
            }
            //Valida que la llave privada este correcta con su contraseña
            $this->validateKeyPem($path_file_key_pass_pem, $password_key);
            //Valida que la llave privada pertenezca al certificado
            $this->validateKeyBelongToCer($path_file_key_pass_pem, $password_key, $path_file_cer_pem);
            //Genera informacion
            $data = [
                'password_key' => Crypt::encryptString($password_key),
                'file_key' => $name_file_key . '.pass.pem',
                'path_file_key_pem' => $path_file_key_pem,
            ];
            //Eliminar archivo
            //\Storage::delete(Company::PATH_FILES . '/' . $name_file_key);
        }
        return $data;
    }

    /**
     * Valida que la contraseña este correcta con la llave en formato PEM
     *
     * @param $path_file_key_pass_pem
     * @param $password_key
     * @return bool
     * @throws \Exception
     */
    public function validateKeyPem($path_file_key_pass_pem, $password_key)
    {
        if (\Storage::exists($path_file_key_pass_pem)) {
            $pkeyid = openssl_pkey_get_private(\Storage::get($path_file_key_pass_pem), $password_key);
            if ($pkeyid == false) {
                throw new \Exception(__('base/company.error_file_key_validate_password'));
            } else {
                openssl_free_key($pkeyid);
                return true;
            }
        }
    }

    /**
     * Valida que que el archivo cer pertenezca al archivo key
     *
     * @param $path_file_key_pass_pem
     * @param $password_key
     * @param $path_file_cer_pem
     * @return bool
     * @throws \Exception
     */
    public function validateKeyBelongToCer($path_file_key_pass_pem, $password_key, $path_file_cer_pem)
    {
        if (\Storage::exists($path_file_key_pass_pem) && \Storage::exists($path_file_cer_pem)) {
            $text_test = 'Test CFDI 3.3';
            $pkeyid = openssl_pkey_get_private(\Storage::get($path_file_key_pass_pem), $password_key);
            $pubkeyid = openssl_pkey_get_public(\Storage::get($path_file_cer_pem));
            openssl_sign($text_test, $crypttext, $pkeyid, OPENSSL_ALGO_SHA256);
            $ok = openssl_verify($text_test, $crypttext, $pubkeyid, OPENSSL_ALGO_SHA256);
            openssl_free_key($pkeyid);
            if ($ok == 1) {
                return true;
            } else {
                throw new \Exception(__('base/company.error_file_key_validate_belong_to_file_cer'));
            }
        }
    }

    /**
     * Crea archivo pfx
     *
     * @param $path_file_key_pass_pem
     * @param $password_key
     * @param $path_file_cer_pem
     * @return bool
     * @throws \Exception
     */
    public function createPfx($company,$path_file_key_pem, $password_key, $path_file_cer_pem)
    {
        $data = [];
        if (\Storage::exists($path_file_key_pem) && \Storage::exists($path_file_cer_pem)) {
            $name_file_pfx = Str::random(40) . '.pfx';
            $path_file_pfx = Helper::setDirectory(Company::PATH_FILES,$company->id) . '/' . $name_file_pfx;
            //Crear archivo PFX
            if(config('app.shared_hosting')){
                system('openssl pkcs12 -export -inkey "' . \Storage::path($path_file_key_pem) . '" -in "' . \Storage::path($path_file_cer_pem) . '" -out "' . \Storage::path($path_file_pfx) . '" -passout pass:"' . $password_key.'"');
            }else {
                $process = new Process('openssl pkcs12 -export -inkey "' . \Storage::path($path_file_key_pem) . '" -in "' . \Storage::path($path_file_cer_pem) . '" -out "' . \Storage::path($path_file_pfx) . '" -passout pass:"' . $password_key.'"');
                $process->run();
                if (!$process->isSuccessful()) {
                    throw new ProcessFailedException($process);
                }
            }
            //Genera informacion
            $data = [
                'file_pfx' => $name_file_pfx,
            ];
        }
        return $data;
    }
}
