<?php

namespace App\Services\Api\V1;

use App\Jobs\ProcessRandomAssignLicenseKeysEndpointCalls;
use App\Models\Activation;
use App\Models\Contract;
use App\Models\Downloadable;
use App\Models\DownloadLog;
use App\Models\Generator;
use App\Models\LicenseKey;
use App\Models\Meta;
use App\Models\Product;
use App\Models\Telemetry;
use App\Rules\ValidSqlDateTime;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use LaravelIdea\Helper\App\Models\_IH_LicenseKey_C;
use Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Validator;

class ResponseService {

	public static function respond( array $data, $code = 200 ): JsonResponse {
		$authenticationMethod = getOption( 'api', 'authenticationMethod', 'sign' );

		if ( $authenticationMethod == 'sign' ) {
			return ResponseService::sign( $data, $code );
		}

		return ResponseService::encrypt( $data, $code );
	}

	public static function sign( array $data, $code = 200 ): JsonResponse {
		$private_key    = '';
		$passphrase     = '';
		$algorithm      = '';
		$privateKeyUsed = '';

		if ( isset( $data['product_id'] ) && (int) $data['product_id'] > 0 ) {
			$product = Product::select( [ 'passphrase', 'private_key', 'algorithm' ] )->where( 'id', $data['product_id'] )->first();

			$private_key    = $product->private_key;
			$passphrase     = $product->passphrase;
			$algorithm      = $product->algorithm;
			$privateKeyUsed = 'product';
		}

		if ( $passphrase == '' || ! in_array( $algorithm, array( 1, 2, 3, 6, 7, 8, 9, 10 ) ) ) {
			$private_key    = getOption( 'api', 'privateKey', '' );
			$passphrase     = getOption( 'api', 'passphrase', '' );
			$algorithm      = getOption( 'api', 'algorithm', '' );
			$privateKeyUsed = 'global';
		}

		list( $data, $response, $signature ) = sign( $data['response'], $private_key, $passphrase, $algorithm );

		return response()->json( [
			'response'         => $data,
			'response_base64'  => $response,
			'private_key_used' => $privateKeyUsed,
			'signature'        => $signature
		], $code );
	}

	public static function encrypt( array $data, $code = 200 ): JsonResponse {
		$private_key    = '';
		$passphrase     = '';
		$algorithm      = '';
		$privateKeyUsed = '';

		if ( isset( $data['product_id'] ) && (int) $data['product_id'] > 0 ) {
			$product = Product::select( [ 'passphrase', 'private_key', 'algorithm' ] )->where( 'id', $data['product_id'] )->first();

			$private_key    = $product->private_key;
			$passphrase     = $product->passphrase;
			$algorithm      = $product->algorithm;
			$privateKeyUsed = 'product';
		}

		if ( $passphrase == '' || ! in_array( $algorithm, array( 1, 2, 3, 6, 7, 8, 9, 10 ) ) ) {
			$private_key    = getOption( 'api', 'privateKey', '' );
			$passphrase     = getOption( 'api', 'passphrase', '' );
			$algorithm      = getOption( 'api', 'algorithm', '' );
			$privateKeyUsed = 'global';
		}

		$encryptedData = encryptData( $data['response'], $private_key, $passphrase, $algorithm );

		return response()->json( [
			'response'         => $encryptedData,
			'private_key_used' => $privateKeyUsed,
		], $code );
	}

	public static function verify( $textLicenseKey, $identifier, $internalUse = false, $email = '' ): array {
		$licenseKey = LicenseKey::where( 'license_key', $textLicenseKey )->first();

		if ( $licenseKey ) {
			$product = Product::select( [ 'status' ] )->where( 'id', $licenseKey->product_id )->first();

			if ( $identifier == '' ) {
				return [
					'product_id' => $licenseKey->product_id,
					'response'   => [
						'code'    => 215,
						'message' => 'Identifier is required',
					]
				];
			}

			if ( $product && $product->status == 'active' ) {
				if ( $licenseKey->status == 'available' ) {
					return [
						'product_id' => $licenseKey->product_id,
						'response'   => [
							'code'    => 201,
							'message' => 'This license key is not assigned',
						]
					];
				} else if ( $licenseKey->status == 'blocked' ) {
					return [
						'product_id' => $licenseKey->product_id,
						'response'   => [
							'code'    => 204,
							'message' => 'License key blocked',
						]
					];
				} else if ( $licenseKey->isExpired() ) {
					return [
						'product_id' => $licenseKey->product_id,
						'response'   => [
							'code'    => 205,
							'message' => 'License key expired',
						]
					];
				} else {
					$activation = Activation::where( [
						'license_key_id' => $licenseKey->id,
						'identifier'     => $identifier,
						'status'         => 'active'
					] )->count();

					if ( $activation == 0 ) {
						return [
							'product_id' => $licenseKey->product_id,
							'response'   => [
								'code'    => 202,
								'message' => 'No activation found for this license key/identifier combination',
							]
						];
					}

					if ( $internalUse ) {
						return [
							'product_id' => $licenseKey->product_id,
							'response'   => [
								'code'        => 200,
								'message'     => 'Valid license key',
								'license_key' => $licenseKey
							]
						];
					}

					return [
						'product_id' => $licenseKey->product_id,
						'response'   => [
							'code'    => 200,
							'message' => 'Valid license key',
						]
					];
				}
			}

			return [
				'product_id' => $licenseKey->product_id,
				'response'   => [
					'code'    => 203,
					'message' => "The product associated with this license key is not active or doesn't exist"
				]
			];
		} else if ( isEnvatoPurchaseCode( $textLicenseKey ) ) {
			$purchase = getEnvatoPurchase( $textLicenseKey );

			if ( isset( $purchase['item'] ) ) {
				$product = Product::where( 'external_reference', $purchase['item']['id'] )->first();
				if ( getOption( 'envato', 'createProduct' ) ) {
					if ( ! $product ) {
						$product = new Product();
					}

					$product->source              = 'envato';
					$product->external_reference  = $purchase['item']['id'];
					$product->name                = $purchase['item']['name'];
					$product->description         = $purchase['item']['name'];
					$product->require_non_expired = false;
					$product->status              = 'active';
					$product->algorithm           = '';
					$product->passphrase          = '';
					$product->private_key         = '';

					$product->save();
				}

				$licenseKey                   = new LicenseKey();
				$licenseKey->product_id       = $product->id;
				$licenseKey->license_key      = $textLicenseKey;
				$licenseKey->owner_name       = $purchase['buyer'];
				$licenseKey->owner_email      = $email;
				$licenseKey->validity         = 0;
				$licenseKey->activation_limit = 1;
				$licenseKey->assigned_at      = now();
				$licenseKey->status           = 'assigned';
				$licenseKey->save();

				return [
					'product_id' => $licenseKey->product_id,
					'response'   => [
						'code'    => 206,
						'message' => 'Envato purchase code added',
					]
				];
			}
		}

		return [
			'response' => [
				'code'    => 210,
				'message' => 'Invalid license key',
			]
		];
	}

	public static function activate( $textLicenseKey, $identifier, $ip, $postURL = '', $email = '' ): array {
		$verify = self::verify( $textLicenseKey, $identifier, true, $email );

		if ( $verify['response']['code'] == 202 ) {
			$licenseKey = LicenseKey::select( [ 'id', 'activation_limit' ] )->where( 'license_key', $textLicenseKey )->first();

			if ( $licenseKey->activation_limit == 0 || $licenseKey->activation_limit > Activation::where( [ 'license_key_id' => $licenseKey->id, 'status' => 'active' ] )->count() ) {
				$activation = new Activation();

				$activation->license_key_id     = $licenseKey->id;
				$activation->ip_address         = $ip;
				$activation->identifier         = $identifier;
				$activation->post_url           = $postURL;
				$activation->activation_date    = now();
				$activation->status             = 'active';
				$activation->activation_counter = 1;
				$activation->save();

				return [
					'product_id' => $licenseKey->product_id,
					'response'   => [
						'code'    => 300,
						'message' => 'License key activated',
					]
				];
			} else {
				return [
					'product_id' => $licenseKey->product_id,
					'response'   => [
						'code'    => 302,
						'message' => 'License key reached activation limit',
					]
				];
			}
		} else if ( $verify['response']['code'] == 200 ) {
			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'    => 301,
					'message' => 'License key already active',
				]
			];
		}

		return $verify;
	}

	public static function deactivate( $textLicenseKey, $identifier ): array {
		$verify = self::verify( $textLicenseKey, $identifier );

		if ( $verify['response']['code'] == 200 ) {
			$licenseKey = LicenseKey::select( [ 'id' ] )->where( 'license_key', $textLicenseKey )->first();

			$activation = Activation::where( [
				'license_key_id' => $licenseKey->id,
				'identifier'     => $identifier,
				'status'         => 'active',
			] )->latest()->first();

			$activation->status               = 'inactive';
			$activation->deactivation_counter = $activation->deactivation_counter + 1;
			$activation->updated_at           = now();
			$activation->save();

			return [
				'product_id' => $licenseKey->product_id,
				'response'   => [
					'code'    => '400',
					'message' => 'License key deactivated',
				]
			];
		} else if ( $verify['response']['code'] == 202 ) {
			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'    => 401,
					'message' => 'License key already inactive',
				]
			];
		}

		return $verify;
	}

	public static function getLicenseKeyDetails( $textLicenseKey, $identifier ): array {
		$verify = self::verify( $textLicenseKey, $identifier );

		if ( $verify['response']['code'] == 200 ) {
			$licenseKey = LicenseKey::exclude( [ 'status', 'created_at', 'updated_at' ] )->where( 'license_key', $textLicenseKey )->first();
			$product    = Product::exclude( [ 'algorithm', 'passphrase', 'private_key', 'require_non_expired', 'status', 'created_at', 'updated_at' ] )->where( 'id', $licenseKey['product_id'] )->first();
			$licenseKey = $licenseKey->makeHidden( 'product_id' );

			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'       => 500,
					'message'    => 'Active license key found',
					'licenseKey' => array_merge(
						$licenseKey->toArray(),
						[ 'expires_at' => $licenseKey->getExpiration() ? $licenseKey->getExpiration()->format( 'Y-m-d H:i:s' ) : false ]
					),
					'product'    => $product,
				]
			];
		}

		return $verify;
	}

	public static function accessDownloadables( $textLicenseKey, $identifier, $apiKey ): array {
		$verify = self::verify( $textLicenseKey, $identifier );

		if ( $verify['response']['code'] == 200 ) {
			$licenseKey = LicenseKey::where( 'license_key', $textLicenseKey )->first();

			$downloadables = Downloadable::exclude( [ 'assigned_after', 'assigned_before', 'expires_before', 'accessible_until', 'created_at', 'updated_at' ] )
			                             ->where( 'product_id', $licenseKey->product_id )
			                             ->where( function ( $q ) use ( $licenseKey ) {
				                             if ( $licenseKey->getExpiration() ) {
					                             $q->where( 'expires_before', '>=', $licenseKey->getExpiration()->format( 'Y-m-d H:i:s' ) )
					                               ->orWhereNull( 'expires_before' );
				                             }
			                             } )
			                             ->where( function ( $q ) use ( $licenseKey ) {
				                             if ( $licenseKey->assigned_at ) {
					                             $q->where( 'assigned_after', '<=', $licenseKey->assigned_at )
					                               ->orWhereNull( 'assigned_after' );
				                             }
			                             } )
			                             ->where( function ( $q ) use ( $licenseKey ) {
				                             if ( $licenseKey->assigned_at ) {
					                             $q->where( 'assigned_before', '>=', $licenseKey->assigned_at )
					                               ->orWhereNull( 'assigned_before' );
				                             }
			                             } )
			                             ->where( function ( $q ) {
				                             $q->where( 'accessible_until', '>', now() )
				                               ->orWhereNull( 'accessible_until' );
			                             } )
			                             ->orderBy( 'numeric_version', 'DESC' )
			                             ->get()
			                             ->toArray();

			foreach ( $downloadables as $key => $value ) {
				$downloadables[ $key ]['url'] = route( 'access-downloadables.download', [
					'api_key'     => $apiKey,
					'license_key' => $textLicenseKey,
					'identifier'  => urlencode( base64_encode( $identifier ) ),
					'version'     => $value['numeric_version']
				] );
			}

			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'          => 600,
					'message'       => 'Downloads',
					'downloadables' => $downloadables
				]
			];
		}

		return $verify;
	}

	public static function download( $textLicenseKey, $identifier, $version ): JsonResponse|StreamedResponse {
		$acceptedUseragent = getOption( 'api', 'acceptedUseragent', '' );
		if ( $acceptedUseragent == '' || $acceptedUseragent == request()->userAgent() ) {


			$identifier = base64_decode( urldecode( $identifier ) );
			$verify     = self::verify( $textLicenseKey, $identifier );

			if ( $verify['response']['code'] == 200 ) {
				$licenseKey = LicenseKey::where( 'license_key', $textLicenseKey )->first();

				if ( ( $licenseKey->product->require_non_expired && ! $licenseKey->isExpired() ) || ! $licenseKey->product->require_non_expired ) {
					$downloadables = Downloadable::select( [ 'id', 'download_counter' ] )
					                             ->where( 'product_id', $licenseKey->product_id )
					                             ->where( function ( $q ) use ( $licenseKey ) {
						                             if ( $licenseKey->getExpiration() ) {
							                             $q->where( 'expires_before', '>=', $licenseKey->getExpiration()->format( 'Y-m-d H:i:s' ) );
						                             }
					                             } )
					                             ->where( function ( $q ) use ( $licenseKey ) {
						                             if ( $licenseKey->assigned_at ) {
							                             $q->where( 'assigned_after', '<=', $licenseKey->assigned_at )
							                               ->orWhereNull( 'assigned_after' );
						                             }
					                             } )
					                             ->where( function ( $q ) use ( $licenseKey ) {
						                             if ( $licenseKey->assigned_at ) {
							                             $q->where( 'assigned_before', '>=', $licenseKey->assigned_at )
							                               ->orWhereNull( 'assigned_before' );
						                             }
					                             } )
					                             ->where( function ( $q ) {
						                             $q->where( 'accessible_until', '>', now() )
						                               ->orWhereNull( 'accessible_until' );
					                             } )
					                             ->where( 'numeric_version', $version )
					                             ->orderBy( 'numeric_version', 'DESC' )
					                             ->first();

					if ( $downloadables ) {
						if ( Storage::fileExists( 'downloadables/' . $downloadables->id . '.zip' ) ) {
							Downloadable::where( 'id', $downloadables->id )->update( [ 'download_counter' => $downloadables->download_counter + 1 ] );

							$downloadLog                  = new DownloadLog();
							$downloadLog->downloadable_id = $downloadables->id;
							$downloadLog->ip_address      = request()->ip();
							$downloadLog->user_agent      = request()->userAgent();
							$downloadLog->save();

							return Storage::download( 'downloadables/' . $downloadables->id . '.zip' );
						} else {
							return ResponseService::respond( [
								'response' => [
									'code'    => 700,
									'message' => "The request file doesn't exist",
								]
							] );
						}
					}
				} else {
					return ResponseService::respond( [
						'response' => [
							'code'    => 701,
							'message' => "You don't have permission to access it",
						]
					] );
				}
			}

			return ResponseService::respond( $verify );
		}

		return ResponseService::respond( [
			'response' => [
				'code'    => 150,
				'message' => __( 'Access denied' )
			]
		], 403 );
	}

	public static function assignLicenseKey( $licenseKey, $ownerName, $ownerEmail ): array {
		$licenseKey = self::getUnassignedLicenseKey( $licenseKey );
		if ( $licenseKey ) {
			$licenseKey->status      = 'assigned';
			$licenseKey->owner_name  = $ownerName;
			$licenseKey->owner_email = $ownerEmail;
			$licenseKey->assigned_at = now();
			$licenseKey->updated_at  = now();

			$licenseKey->save();

			return [
				'product_id' => $licenseKey->product_id,
				'response'   => [
					'code'    => 800,
					'message' => 'License key assigned',
				]
			];
		}

		return [
			'response' => [
				'code'    => 801,
				'message' => 'Invalid or already assigned license key',
			]
		];
	}

	public static function randomAssignLicenseKey( $productId, $ownerName, $ownerEmail, $quantity, $generate ): array {
		$product = Product::find( $productId );

		if ( ! $product ) {
			return [
				'response' => [
					'code'    => 805,
					'message' => 'Product not found',
				]
			];
		}

		$count = LicenseKey::where( [ 'product_id' => $productId, 'status' => 'available' ] )->whereNull( 'contract_id' )->count();

		if ( ! $generate && $count < $quantity ) {
			return [
				'response' => [
					'code'               => 802,
					'message'            => 'Insufficient license keys',
					'available_quantity' => $count
				]
			];
		}

		$licenseKeys = LicenseKey::where( 'status', 'available' )->whereNull( 'contract_id' )->take( $quantity )->get();
		if ( $licenseKeys ) {
			foreach ( $licenseKeys as $licenseKey ) {
				$licenseKey->status      = 'assigned';
				$licenseKey->owner_name  = $ownerName;
				$licenseKey->owner_email = $ownerEmail;
				$licenseKey->assigned_at = now();
				$licenseKey->updated_at  = now();

				$licenseKey->save();
			}
		}

		$generatedLicenseKeys = [];
		$generateQuantity     = abs( $quantity - $licenseKeys->count() );
		if ( $generate && $generateQuantity > 0 ) {
			$generator = Generator::where( [ 'product_id' => $productId, 'status' => '1' ] )->first();

			if ( ! $generator ) {
				return [
					'response' => [
						'code'    => 806,
						'message' => 'Generator not found',
					]
				];
			}

			$generatedLicenseKeys = self::generateLicenseKeys( $generator, $generateQuantity, false, 'assigned', $ownerName, $ownerEmail );
		}

		return [
			'response' => [
				'code'         => 803,
				'message'      => 'License keys assigned',
				'license_keys' => array_merge( $licenseKeys->toArray(), $generatedLicenseKeys )
			]
		];
	}

	public static function randomAssignLicenseKeysQueued( $productId, $ownerName, $ownerEmail, $quantity, $generate, $webhookUrl, $identifier ): array {
		ProcessRandomAssignLicenseKeysEndpointCalls::dispatch( $productId, $ownerName, $ownerEmail, $quantity, $generate, $webhookUrl, $identifier )->onQueue( 'random-assign-license-keys' );

		return [
			'response' => [
				'code'    => 807,
				'message' => 'Request Queued',
			]
		];
	}

	public static function createLicenseKey( $licenseKeys, $options ): array {
		if ( $licenseKeys ) {
			$validator = Validator::make( $options, [
				'product_id'       => 'required|exists:products,id',
				'owner_email'      => 'email|nullable',
				'activation_limit' => 'required|numeric|min:0',
				'validity'         => 'required|numeric|min:0',
				'assigned_at'      => [ new ValidSqlDateTime() ],
				'status'           => [
					'required',
					Rule::in( array_keys( LicenseKey::getStatuses() ) )
				]
			] );

			$result = [
				'created'   => 0,
				'duplicate' => 0,
			];

			if ( $validator->passes() ) {
				foreach ( $licenseKeys as $item ) {
					if ( LicenseKey::where( 'license_key', $item )->count() == 0 ) {
						$licenseKey = new LicenseKey();
						$licenseKey->fill( $options );
						$licenseKey->license_key = $item;

						$licenseKey->save();

						$result['created'] ++;
					} else {
						$result['duplicate'] ++;
					}
				}

				return [
					'product_id' => $options['product_id'],
					'response'   => array_merge( [
						'code'    => 900,
						'message' => 'License keys creation result',
					], $result )
				];
			}

			return [
				'response' => [
					'code'    => 901,
					'message' => 'Incorrect data found',
					'errors'  => $validator->errors()
				]
			];
		}

		return [
			'response' => [
				'code'    => 902,
				'message' => 'No license keys created',
			]
		];
	}

	public static function updateLicenseKey( $text_licenseKey, $options ): array {
		$licenseKey = LicenseKey::where( 'license_key', $text_licenseKey )->first();

		if ( $licenseKey ) {
			$productId = $licenseKey->product_id;
			$options   = array_merge(
				$licenseKey->toArray(),
				array_filter( $options, function ( $item ) {
					return $item !== null;
				} )
			);

			$validator = Validator::make( $options, [
				'product_id'       => 'required',
				'license_key'      => [
					'required',
					Rule::unique( 'license_keys', 'license_key' )->where( function ( $query ) use ( $productId ) {
						return $query->where( 'product_id', $productId );
					} )->ignore( $licenseKey->id )
				],
				'owner_email'      => 'email|nullable',
				'activation_limit' => 'required|numeric|min:0',
				'validity'         => 'required|numeric|min:0',
				'assigned_at'      => [ new ValidSqlDateTime() ],
				'status'           => [
					'required',
					Rule::in( array_keys( LicenseKey::getStatuses() ) )
				]
			] );

			if ( $validator->passes() ) {
				$licenseKey->fill( $options );
				$licenseKey->save();

				return [
					'product_id' => $options['product_id'],
					'response'   => array_merge( [
						'code'    => 950,
						'message' => 'License keys updated',
					] )
				];
			}

			return [
				'response' => [
					'code'    => 951,
					'message' => 'Incorrect data found',
					'errors'  => $validator->errors()
				]
			];
		}

		return [
			'response' => [
				'code'    => 952,
				'message' => 'License key not found',
			]
		];
	}

	public static function deleteLicenseKey( $textLicenseKey ): array {
		$licenseKey = LicenseKey::where( [ 'license_key' => $textLicenseKey, ] )->first();

		if ( $licenseKey ) {
			$productId = $licenseKey->product_id;
			$licenseKey->delete();

			return [
				'product_id' => $productId,
				'response'   => [
					'code'    => 850,
					'message' => 'License key deleted',
				]
			];
		}

		return [
			'response' => [
				'code'    => 851,
				'message' => 'License key not found',
			]
		];
	}

	public static function createProduct( $options ): array {

		$validator = Validator::make( $options, [
			'external_reference' => 'required_if:source,envato',
			'source'             => 'required',
			'name'               => 'required',
			'description'        => 'required',
			'status'             => [ 'required', Rule::in( [ 'active', 'inactive' ] ) ],
		] );

		if ( $validator->passes() ) {
			$product = new Product();
			$product->fill( $options );

			$product->algorithm   = '';
			$product->passphrase  = '';
			$product->private_key = '';

			$product->save();

			return [
				'response' => array_merge( [
					'code'       => 750,
					'message'    => 'Product created',
					'product_id' => $product->id
				] )
			];
		}

		return [
			'response' => [
				'code'    => 751,
				'message' => 'Incorrect data found',
				'errors'  => $validator->errors()
			]
		];
	}

	public static function updateProduct( $options ): array {

		$validator = Validator::make( $options, [
			'product_id'         => 'exists:products,id',
			'external_reference' => 'required_if:source,envato',
			'source'             => 'required',
			'name'               => 'required',
			'description'        => 'required',
			'status'             => [ 'required', Rule::in( [ 'active', 'inactive' ] ) ],
		] );

		if ( $validator->passes() ) {
			$product = Product::find( $options['product_id'] );
			$product->fill( $options );

			$product->save();

			return [
				'response' => array_merge( [
					'code'    => 650,
					'message' => 'Product updated'
				] )
			];
		}

		return [
			'response' => [
				'code'    => 651,
				'message' => 'Incorrect data found',
				'errors'  => $validator->errors()
			]
		];
	}

	public static function deleteProduct( $product_id ): array {
		$product = Product::find( $product_id );

		if ( $product ) {
			$product->delete();

			return [
				'response' => [
					'code'    => 550,
					'message' => 'Product deleted',
				]
			];
		}

		return [
			'response' => [
				'code'    => 551,
				'message' => 'Product not found',
			]
		];
	}

	public static function createLicenseKeyMeta( $textLicenseKey, $identifier, $key, $value ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 450,
					'message' => "Meta key is required",
				]
			];
		}

		if ( $value == '' ) {
			return [
				'response' => [
					'code'    => 451,
					'message' => "Meta value is required",
				]
			];
		}

		$verify = self::verify( $textLicenseKey, $identifier, true );

		if ( $verify['response']['code'] == 200 ) {
			if ( Meta::where( [ 'type' => 'license_key', 'item_id' => $verify['response']['license_key']->id, 'key' => $key ] )->count() > 0 ) {
				return [
					'response' => [
						'code'    => 452,
						'message' => "Meta key already exists",
					]
				];
			}

			$meta          = new Meta();
			$meta->type    = 'license_key';
			$meta->item_id = $verify['response']['license_key']->id;
			$meta->key     = $key;
			$meta->value   = $value;

			$meta->save();

			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'    => 453,
					'message' => 'Metadata created',
					'meta_id' => $meta->id
				]
			];
		}

		return $verify;
	}

	public static function updateLicenseKeyMeta( $textLicenseKey, $identifier, $key, $value ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 350,
					'message' => "Meta key is required",
				]
			];
		}

		if ( $value == '' ) {
			return [
				'response' => [
					'code'    => 351,
					'message' => "Meta value is required",
				]
			];
		}

		$verify = self::verify( $textLicenseKey, $identifier, true );

		if ( $verify['response']['code'] == 200 ) {
			if ( Meta::where( [ 'type' => 'license_key', 'item_id' => $verify['response']['license_key']->id, 'key' => $key ] )->count() == 0 ) {
				return [
					'response' => [
						'code'    => 352,
						'message' => "Meta key doesn't exist",
					]
				];
			}

			$meta        = Meta::where( [ 'type' => 'license_key', 'item_id' => $verify['response']['license_key']->id, 'key' => $key ] )->first();
			$meta->value = $value;

			$meta->save();

			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'    => 353,
					'message' => 'Metadata updated',
					'meta_id' => $meta->id
				]
			];
		}

		return $verify;
	}

	public static function deleteLicenseKeyMeta( $textLicenseKey, $identifier, $key ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 250,
					'message' => "Meta key is required",
				]
			];
		}

		$verify = self::verify( $textLicenseKey, $identifier, true );

		if ( $verify['response']['code'] == 200 ) {
			if ( Meta::where( [ 'type' => 'license_key', 'item_id' => $verify['response']['license_key']->id, 'key' => $key ] )->count() == 0 ) {
				return [
					'response' => [
						'code'    => 251,
						'message' => "Meta key doesn't exist",
					]
				];
			}

			Meta::where( [ 'type' => 'license_key', 'item_id' => $verify['response']['license_key']->id, 'key' => $key ] )->delete();

			return [
				'product_id' => $verify['product_id'],
				'response'   => [
					'code'    => 252,
					'message' => 'Metadata deleted'
				]
			];
		}

		return $verify;
	}

	public static function createProductMeta( $product_id, $key, $value ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 270,
					'message' => "Meta key is required",
				]
			];
		}

		if ( $value == '' ) {
			return [
				'response' => [
					'code'    => 271,
					'message' => "Meta value is required",
				]
			];
		}

		if ( Meta::where( [ 'type' => 'product', 'item_id' => $product_id, 'key' => $key ] )->count() > 0 ) {
			return [
				'product_id' => $product_id,
				'response'   => [
					'code'    => 272,
					'message' => "Meta key already exists",
				]
			];
		}

		$product = Product::find( $product_id );

		if ( $product ) {
			$meta          = new Meta();
			$meta->type    = 'product';
			$meta->item_id = $product_id;
			$meta->key     = $key;
			$meta->value   = $value;

			$meta->save();

			return [
				'product_id' => $product_id,
				'response'   => array_merge( [
					'code'    => 273,
					'message' => 'Product meta created'
				] )
			];
		}

		return [
			'response' => [
				'code'    => 274,
				'message' => 'Product not found'
			]
		];
	}

	public static function updateProductMeta( $product_id, $key, $value ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 370,
					'message' => "Meta key is required",
				]
			];
		}

		if ( $value == '' ) {
			return [
				'response' => [
					'code'    => 371,
					'message' => "Meta value is required",
				]
			];
		}

		if ( Meta::where( [ 'type' => 'product', 'item_id' => $product_id, 'key' => $key ] )->count() == 0 ) {
			return [
				'product_id' => $product_id,
				'response'   => [
					'code'    => 372,
					'message' => "Meta key doesn't exists",
				]
			];
		}

		$product = Product::find( $product_id );

		if ( $product ) {
			$meta        = Meta::where( [ 'type' => 'product', 'item_id' => $product_id, 'key' => $key ] )->first();
			$meta->value = $value;

			$meta->save();

			return [
				'product_id' => $product_id,
				'response'   => array_merge( [
					'code'    => 373,
					'message' => 'Product meta updated'
				] )
			];
		}

		return [
			'response' => [
				'code'    => 374,
				'message' => 'Product not found'
			]
		];
	}

	public static function deleteProductMeta( $product_id, $key ): array {
		if ( $key == '' ) {
			return [
				'response' => [
					'code'    => 480,
					'message' => "Meta key is required",
				]
			];
		}

		if ( Meta::where( [ 'type' => 'product', 'item_id' => $product_id, 'key' => $key ] )->count() == 0 ) {
			return [
				'product_id' => $product_id,
				'response'   => [
					'code'    => 481,
					'message' => "Meta key doesn't exists",
				]
			];
		}

		$product = Product::find( $product_id );

		if ( $product ) {
			Meta::where( [ 'type' => 'product', 'item_id' => $product_id, 'key' => $key ] )->delete();

			return [
				'product_id' => $product_id,
				'response'   => array_merge( [
					'code'    => 482,
					'message' => 'Product meta deleted'
				] )
			];
		}

		return [
			'response' => [
				'code'    => 483,
				'message' => 'Product not found'
			]
		];
	}

	public static function getProducts(): array {
		$products = Product::exclude( [ 'algorithm', 'passphrase', 'private_key', ] )->get();

		return [
			'response' => [
				'code'     => 810,
				'message'  => 'All products',
				'products' => $products,
			]
		];
	}

	public static function getGenerators(): array {
		return [
			'response' => [
				'code'     => 820,
				'message'  => 'All generators',
				'products' => Generator::all()
			]
		];
	}

	public static function generate( $generatorId, $quantity ): array {
		$licenseKeys = [];
		$generator   = Generator::find( $generatorId );
		$product_id  = 0;

		if ( $generator ) {
			$licenseKeys = self::generateLicenseKeys( $generator, $quantity, true );
			$product_id  = $generator->product_id;
		}

		return [
			'product_id' => $product_id,
			'response'   => [
				'code'        => 815,
				'message'     => 'Generated license keys',
				'licenseKeys' => $licenseKeys
			]
		];
	}

	public static function getLicenseKeys( $options ): array {
		$licenseKeys = LicenseKey::where( function ( $q ) use ( $options ) {
			if ( $options['product_id'] > 0 ) {
				$q->where( 'product_id', $options['product_id'] );
			}

			if ( $options['status'] > '' ) {
				$q->where( 'status', $options['status'] );
			}

			if ( $options['owner_email'] > '' ) {
				$q->where( 'owner_email', $options['owner_email'] );
			}

			if ( $options['owner_name'] > '' ) {
				$q->where( 'owner_name', $options['owner_name'] );
			}
		} )->paginate( abs( $options['items_per_page'] ), [ '*' ], '', abs( $options['page'] ) )->toArray();

		return [
			'response' => [
				'code'         => 730,
				'message'      => 'License keys',
				'license_keys' => $licenseKeys['data']
			]
		];
	}

	public static function sendTelemetryData( $options ): array {
		$validator = Validator::make( $options, [
			'product_id'                => [ 'required', Rule::exists( 'products', 'id' ) ],
			'user_identifier'           => [ 'required', 'string' ],
			'license_key'               => [ 'required', 'string' ],
			'activation_identifier'     => [ 'required', 'string' ],
			'product_version'           => [ 'required', 'string' ],
			'data_type'                 => [ 'required', Rule::in( Telemetry::$dataTypes ) ],
			'data_group'                => [ 'required', 'string' ],
			'numeric_data_single_value' => [ Rule::when( $options['data_type'] == 'numeric-single-value', [ 'required', 'numeric' ] ), ],
			'numeric_data_x'            => [ Rule::when( $options['data_type'] == 'numeric-xy-axis', [ 'required', 'numeric' ] ) ],
			'numeric_data_y'            => [ Rule::when( $options['data_type'] == 'numeric-xy-axis', [ 'required', 'numeric' ] ) ],
			'text_data'                 => [ Rule::when( $options['data_type'] == 'text', [ 'required', 'string' ] ) ],
		] );

		$telemetry                        = new Telemetry();
		$telemetry->product_id            = $options['product_id'];
		$telemetry->user_identifier       = $options['user_identifier'];
		$telemetry->license_key           = $options['license_key'];
		$telemetry->activation_identifier = $options['activation_identifier'];
		$telemetry->product_version       = $options['product_version'];
		$telemetry->data_type             = $options['data_type'];
		$telemetry->data_group            = $options['data_group'];

		if ( $options['data_type'] == 'numeric-xy-axis' ) {
			$telemetry->data = json_encode( [
				'numeric_x' => (int) $options['numeric_data_x'],
				'numeric_y' => (int) $options['numeric_data_y']
			] );
		} elseif ( $options['data_type'] == 'numeric-single-value' ) {
			$telemetry->data = (int) $options['numeric_data_single_value'];
		} elseif ( $options['data_type'] == 'text' ) {
			$telemetry->data = json_encode( [
				'text' => $options['text_data']
			] );
		} else {
			$telemetry->data = json_encode( [
				'text'      => $options['text_data'],
				'numeric_x' => (int) $options['numeric_data_x'],
				'numeric_y' => (int) $options['numeric_data_y'],
				'numeric_z' => (int) $options['numeric_data_single_value'],
			] );
		}

		$hasRedFlags                       = false;
		$telemetry->product_exists         = true;
		$telemetry->product_version_exists = true;
		$telemetry->license_key_exists     = true;

		if ( Product::where( 'id', $options['product_id'] )->count() == 0 ) {
			$telemetry->product_exists = false;
			$hasRedFlags               = true;
		}

		if ( Downloadable::where( [ 'id' => $options['product_id'], 'version' => $options['product_version'] ] )->count() == 0 ) {
			$telemetry->product_version_exists = false;
			$hasRedFlags                       = true;
		}

		if ( LicenseKey::where( 'license_key', $options['license_key'] )->count() == 0 ) {
			$telemetry->license_key_exists = false;
			$hasRedFlags                   = true;
		}

		if ( $validator->passes() ) {
			$telemetry->is_correct_format = true;
			$telemetry->has_red_flags     = $hasRedFlags;
			$telemetry->save();

			return [
				'response' => [
					'code'    => 830,
					'message' => 'Correct format data saved',
					'errors'  => $validator->errors()
				]
			];
		} else {
			$telemetry->is_correct_format = false;
			$telemetry->has_red_flags     = true;
			$telemetry->save();

			return [
				'response' => [
					'code'    => 831,
					'message' => 'Incorrect format data saved',
					'errors'  => $validator->errors()
				]
			];
		}
	}

	public static function getTelemetryData( $options ): array {
		$data = Telemetry::where( function ( $query ) use ( $options ) {
			if ( (int) $options['product_id'] > 0 ) {
				$query->where( 'product_id', (int) $options['product_id'] );
			}

			if ( $options['user_identifier'] != '' ) {
				$query->where( 'user_identifier', $options['user_identifier'] );
			}

			if ( $options['license_key'] != '' ) {
				$query->where( 'license_key', $options['license_key'] );
			}

			if ( $options['activation_identifier'] != '' ) {
				$query->where( 'activation_identifier', $options['activation_identifier'] );
			}

			if ( $options['product_version'] != '' ) {
				$query->where( 'product_version', $options['product_version'] );
			}

			if ( $options['data_type'] != '' ) {
				$query->where( 'data_type', $options['data_type'] );
			}

			if ( $options['data_group'] != '' ) {
				$query->where( 'data_group', $options['data_group'] );
			}
		} )->get();

		return [
			'response' => [
				'code'    => 970,
				'message' => 'Telemetry data found',
				'data'    => $data
			]
		];
	}

	public static function getAvailableLicenseKeysCount( $product_id ): array {
		if ( ! Product::where( 'id', $product_id )->exists() ) {
			return [
				'response' => [
					'code'    => 740,
					'message' => "Product doesn't exist",
				]
			];
		}

		$count = LicenseKey::where( [ 'product_id' => $product_id, 'status' => 'available' ] )->count();

		return [
			'response' => [
				'code'    => 741,
				'message' => 'Available license keys count',
				'count'   => $count
			]
		];
	}

	public static function createContract( $options ): array {
		$errors = [];

		if ( $options['contract_key'] == '' || Contract::where( 'contract_key', $options['contract_key'] )->exists() ) {
			$errors['contract_key'] = 'Contract key is required and must be unique.';
		}

		if ( $options['contract_name'] == '' ) {
			$errors['contract_name'] = 'Contract name is required.';
		}

		if ( $options['contract_information'] == '' ) {
			$errors['contract_information'] = 'Contract information is required.';
		}

		if ( $options['product_id'] <= 0 || ! Product::where( 'id', $options['product_id'] )->exists() ) {
			$errors['product_id'] = 'Product Id must be a valid product Id.';
		}

		if ( $options['license_keys_quantity'] <= 0 ) {
			$errors['license_keys_quantity'] = 'License keys quantity must be 1 or more.';
		}

		if ( ! in_array( $options['status'], [ 'active', 'inactive' ] ) ) {
			$errors['status'] = 'Status must be active or inactive';
		}

		if ( ! in_array( $options['can_get_info'], [ 1, 0 ] ) ) {
			$errors['can_get_info'] = 'Can get info must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_generate'], [ 1, 0 ] ) ) {
			$errors['can_generate'] = 'Can generate must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_destroy'], [ 1, 0 ] ) ) {
			$errors['can_destroy'] = 'Can destroy must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_destroy_all'], [ 1, 0 ] ) ) {
			$errors['can_destroy_all'] = 'Can destroy all must be 1 for true or 0 for false';
		}

		if ( $errors ) {
			return [
				'response' => [
					'code'    => 825,
					'message' => 'Incorrect data found',
					'errors'  => $errors
				]
			];
		}

		$contract = new Contract();

		$contract->contract_key          = $options['contract_key'];
		$contract->name                  = $options['contract_name'];
		$contract->information           = $options['contract_information'];
		$contract->product_id            = $options['product_id'];
		$contract->license_keys_quantity = $options['license_keys_quantity'];
		$contract->status                = $options['status'];
		$contract->can_get_info          = $options['can_get_info'];
		$contract->can_generate          = $options['can_generate'];
		$contract->can_destroy           = $options['can_destroy'];
		$contract->can_destroy_all       = $options['can_destroy_all'];

		$contract->save();

		return [
			'response' => [
				'code'     => 841,
				'message'  => 'contract created',
				'contract' => $contract
			]
		];
	}

	public static function updateContract( $options ): array {
		$errors = [];

		if ( $options['contract_id'] == '' || ! Contract::where( 'id', $options['contract_id'] )->exists() ) {
			$errors['contract_id'] = 'Contract not found.';
		}

		if ( $options['contract_key'] == '' || Contract::where( 'id', '!=', $options['contract_id'] )->where( 'contract_key', $options['contract_key'] )->exists() ) {
			$errors['contract_key'] = 'Contract key is required and must be unique.';
		}

		if ( $options['contract_name'] == '' ) {
			$errors['contract_name'] = 'Contract name is required.';
		}

		if ( $options['contract_information'] == '' ) {
			$errors['contract_information'] = 'Contract information is required.';
		}

		if ( $options['product_id'] <= 0 || ! Product::where( 'id', $options['product_id'] )->exists() ) {
			$errors['product_id'] = 'Product Id must be a valid product Id.';
		}

		if ( $options['license_keys_quantity'] <= 0 ) {
			$errors['license_keys_quantity'] = 'License keys quantity must be 1 or more.';
		}

		if ( ! in_array( $options['status'], [ 'active', 'inactive' ] ) ) {
			$errors['status'] = 'Status must be active or inactive';
		}

		if ( ! in_array( $options['can_get_info'], [ 1, 0 ] ) ) {
			$errors['can_get_info'] = 'Can get info must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_generate'], [ 1, 0 ] ) ) {
			$errors['can_generate'] = 'Can generate must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_destroy'], [ 1, 0 ] ) ) {
			$errors['can_destroy'] = 'Can destroy must be 1 for true or 0 for false';
		}

		if ( ! in_array( $options['can_destroy_all'], [ 1, 0 ] ) ) {
			$errors['can_destroy_all'] = 'Can destroy all must be 1 for true or 0 for false';
		}

		if ( $errors ) {
			return [
				'response' => [
					'code'    => 825,
					'message' => 'Incorrect data found',
					'errors'  => $errors
				]
			];
		}

		$contract = Contract::find( $options['contract_id'] );

		$contract->contract_key          = $options['contract_key'];
		$contract->name                  = $options['contract_name'];
		$contract->information           = $options['contract_information'];
		$contract->product_id            = $options['product_id'];
		$contract->license_keys_quantity = $options['license_keys_quantity'];
		$contract->status                = $options['status'];
		$contract->can_get_info          = $options['can_get_info'];
		$contract->can_generate          = $options['can_generate'];
		$contract->can_destroy           = $options['can_destroy'];
		$contract->can_destroy_all       = $options['can_destroy_all'];

		$contract->save();

		return [
			'response' => [
				'code'     => 842,
				'message'  => 'contract updated',
				'contract' => $contract
			]
		];
	}

	public static function deleteContract( $contract_id ): array {
		if ( $contract_id == '' || ! Contract::where( 'id', $contract_id )->exists() ) {
			return [
				'response' => [
					'code'    => 844,
					'message' => 'Contract not found',
				]
			];
		}

		Contract::find( $contract_id )->delete();

		return [
			'response' => [
				'code'    => 843,
				'message' => 'contract deleted',
			]
		];
	}

	public static function getContracts(): array {
		$contracts = Contract::all();

		return [
			'response' => [
				'code'      => 846,
				'message'   => 'All contracts',
				'contracts' => $contracts,
			]
		];
	}

	public static function getUnassignedLicenseKey( $textLicenseKey ): LicenseKey|bool {
		$licenseKey = LicenseKey::where( [
			'license_key' => $textLicenseKey,
			'status'      => 'available'
		] )->first();

		if ( $licenseKey ) {
			return $licenseKey;
		}

		return false;
	}

	/**
	 * @param Generator $generator
	 * @param $quantity
	 * @param $returnJustTheKeys
	 * @param string $status
	 * @param string $ownerName
	 * @param string $ownerEmail
	 *
	 * @return array
	 */
	public static function generateLicenseKeys( Generator $generator, $quantity, $returnJustTheKeys, $status = 'available', $ownerName = '', $ownerEmail = '' ): array {
		$licenseKeys = [];

		if ( $generator->method == 'uuid' ) {
			for ( $generated = 0; $generated < $quantity; $generated ++ ) {
				do {
					$generatedLicenseKey = $generator->prefix . Str::uuid()->toString() . $generator->suffix;
				} while ( LicenseKey::where( 'license_key', $generatedLicenseKey )->count() > 0 );

				$licenseKey                   = new LicenseKey();
				$licenseKey->product_id       = $generator->product_id;
				$licenseKey->license_key      = $generatedLicenseKey;
				$licenseKey->activation_limit = $generator->activation_limit;
				$licenseKey->validity         = $generator->validity;
				$licenseKey->status           = $status;
				$licenseKey->owner_name       = $ownerName;
				$licenseKey->owner_email      = $ownerEmail;
				$licenseKey->assigned_at      = now();
				$licenseKey->contract_id      = null;

				$licenseKey->save();

				if ( $returnJustTheKeys ) {
					$licenseKeys[] = $generatedLicenseKey;
				} else {
					$licenseKeys[] = $licenseKey;
				}
			}
		} else if ( $generator->method == 'chunk-system' ) {
			for ( $generated = 0; $generated < $quantity; $generated ++ ) {
				do {
					$generatedLicenseKey = $generator->prefix . generateLicenseKey( $generator->charset, $generator->number_of_chunks, $generator->chunk_length ) . $generator->suffix;
				} while ( LicenseKey::where( 'license_key', $generatedLicenseKey )->count() > 0 );

				$licenseKey                   = new LicenseKey();
				$licenseKey->product_id       = $generator->product_id;
				$licenseKey->license_key      = $generatedLicenseKey;
				$licenseKey->activation_limit = $generator->activation_limit;
				$licenseKey->validity         = $generator->validity;
				$licenseKey->status           = $status;
				$licenseKey->owner_name       = $ownerName;
				$licenseKey->owner_email      = $ownerEmail;
				$licenseKey->assigned_at      = now();
				$licenseKey->contract_id      = null;

				$licenseKey->save();

				if ( $returnJustTheKeys ) {
					$licenseKeys[] = $generatedLicenseKey;
				} else {
					$licenseKeys[] = $licenseKey;
				}
			}
		} else if ( $generator->method == 'custom' ) {
			for ( $generated = 0; $generated < $quantity; $generated ++ ) {
				do {
					$generatedLicenseKey = $generator->prefix . call_user_func( $generator->function_name ) . $generator->suffix;
				} while ( LicenseKey::where( 'license_key', $generatedLicenseKey )->count() > 0 );

				$licenseKey                   = new LicenseKey();
				$licenseKey->product_id       = $generator->product_id;
				$licenseKey->license_key      = $generatedLicenseKey;
				$licenseKey->activation_limit = $generator->activation_limit;
				$licenseKey->validity         = $generator->validity;
				$licenseKey->status           = $status;
				$licenseKey->owner_name       = $ownerName;
				$licenseKey->owner_email      = $ownerEmail;
				$licenseKey->assigned_at      = now();
				$licenseKey->contract_id      = null;

				$licenseKey->save();

				if ( $returnJustTheKeys ) {
					$licenseKeys[] = $generatedLicenseKey;
				} else {
					$licenseKeys[] = $licenseKey;
				}
			}
		}

		return $licenseKeys;
	}
}
