<?php
/**
 * High-level Google Sheets integration for MetForm submissions.
 *
 * @package MetForm_Pro\Core\Integrations\Google_Sheet
 */

namespace MetForm_Pro\Core\Integrations\Google_Sheet;

use MetForm\Traits\Singleton;
use MetForm\Core\Admin\Base;

defined( 'ABSPATH' ) || exit;

/**
 * Maps MetForm form submissions to rows in a linked Google Spreadsheet.
 *
 * Responsible for:
 * - Lazily creating (and remembering) a spreadsheet per form.
 * - Keeping the header row in sync with the form's field set.
 * - Writing each submission as an appended row.
 */
class WF_Google_Sheet {
	use Singleton;

	/**
	 * Google OAuth client ID, from plugin settings.
	 *
	 * @var string
	 */
	public $google_client_id;

	/**
	 * Google OAuth client secret, from plugin settings.
	 *
	 * @var string
	 */
	public $google_client_secret;

	/**
	 * API client used for all Sheets/Drive calls.
	 *
	 * @var Google_API_Client
	 */
	private $api;

	/**
	 * Loads credentials from settings and boots the API client.
	 */
	public function __construct() {
		$settings = Base::instance()->get_settings_option();

		$this->google_client_id     = isset( $settings['mf_google_sheet_client_id'] ) ? $settings['mf_google_sheet_client_id'] : '';
		$this->google_client_secret = isset( $settings['mf_google_sheet_client_secret'] ) ? $settings['mf_google_sheet_client_secret'] : '';
		$this->api                  = new Google_API_Client( $this->google_client_id, $this->google_client_secret );
	}

	/**
	 * Creates a new spreadsheet and writes the header row.
	 *
	 * @param string   $title Spreadsheet title.
	 * @param string[] $names Column header names (form field keys).
	 * @return string|false New spreadsheet ID, or false on failure.
	 */
	public function create( $title, $names ) {
		$result = $this->api->create_spreadsheet( $title );

		if ( is_wp_error( $result ) || empty( $result['spreadsheetId'] ) ) {
			$this->log_error( is_wp_error( $result ) ? $result : new \WP_Error( 'google_missing_spreadsheet_id', 'Google did not return a spreadsheet ID.' ) );
			return false;
		}

		$this->insert_names( $result['spreadsheetId'], $names );

		return $result['spreadsheetId'];
	}

	/**
	 * Gets the spreadsheet ID linked to a form, creating one if it doesn't exist yet.
	 *
	 * @param int|string $form_id Form ID.
	 * @param string[]   $names   Column header names.
	 * @param string     $title   Title to use if a new spreadsheet needs to be created.
	 * @return string|false Spreadsheet ID, or false on failure.
	 */
	public function get_sheet_id( $form_id, $names, $title ) {
		$option = 'wf_google_sheet_' . $form_id;
		$sheet  = get_option( $option );

		if ( $sheet ) {
			return $sheet;
		}

		$sheet = $this->create( $title, $names );

		if ( $sheet ) {
			add_option( $option, $sheet );
		}

		return $sheet;
	}

	/**
	 * Recomputes and persists the ordered list of field-name columns for a form,
	 * appending any newly added fields to the end.
	 *
	 * @param int|string $form_id     Form ID.
	 * @param array      $form_fields Current form fields, keyed by field name.
	 * @return string[] Updated column name list.
	 */
	public function update_names( $form_id, $form_fields ) {
		$option = 'wf_google_sheet_' . $form_id . 'names';
		$names  = get_option( $option );

		if ( $names ) {
			foreach ( $names as $name ) {
				unset( $form_fields[ $name ] );
			}
		} else {
			$names = array();
		}

		foreach ( $form_fields as $key => $field ) {
			$names[] = $key;
		}

		update_option( $option, $names );

		return $names;
	}

	/**
	 * Writes a single form submission as a new row in its linked spreadsheet.
	 *
	 * @param int|string $form_id          Form ID.
	 * @param string     $form_title       Form title, used if a spreadsheet needs creating.
	 * @param array      $form_data        Submitted field values, keyed by field name.
	 * @param array|null $file_upload_info Uploaded file info, keyed by field name.
	 * @param array      $form_fields      Current form field definitions, keyed by field name.
	 * @param array|null $sheetdata        Optional override: `sheet_id` / `sheet_title_no`.
	 * @return array|false API response on success, false on failure.
	 */
	public function insert( $form_id, $form_title, $form_data, $file_upload_info, $form_fields, $sheetdata = null ) {
		if ( isset( $form_data['mf-mobile'] ) ) {
			$form_data['mf-mobile'] = ltrim( $form_data['mf-mobile'], '+' );
		}

		$names = get_option( 'wf_google_sheet_' . $form_id . 'names' );

		if ( ! $names || count( $names ) !== count( $form_fields ) ) {
			$names = $this->update_names( $form_id, $form_fields );
		}

		if ( isset( $file_upload_info ) ) {
			foreach ( $file_upload_info as $key => $files ) {
				$url = '';
				foreach ( $files as $file ) {
					$url .= isset( $file['url'] ) ? $file['url'] . ' , ' : '';
				}
				$form_data[ $key ] = $url;
			}
		}

		$values = $this->build_row_values( $names, $form_data, $form_fields );

		$sheet_id = ! empty( $sheetdata['sheet_id'] ) ? $sheetdata['sheet_id'] : $this->get_sheet_id( $form_id, $names, $form_title );

		if ( ! $sheet_id ) {
			return false;
		}

		$sheet_title = ! empty( $sheetdata['sheet_title_no'] ) ? $sheetdata['sheet_title_no'] : '';
		$range       = $sheet_title ? $sheet_title . '!A2:Z2' : 'A2:Z2';

		if ( $sheet_title ) {
			$this->insert_names( $sheet_id, $names, $sheet_title );
		}

		$result = $this->api->append_values( $sheet_id, $range, array( $values ) );

		if ( is_wp_error( $result ) ) {
			$this->log_error( $result );
			return false;
		}

		return $result;
	}

	/**
	 * Builds the ordered row of values for a submission, handling the
	 * simple-repeater field's flattened, multi-row-per-entry format.
	 *
	 * @param string[] $names       Ordered column names.
	 * @param array    $form_data   Submitted field values.
	 * @param array    $form_fields Current form field definitions.
	 * @return array Flat list of cell values, in column order.
	 */
	private function build_row_values( $names, $form_data, $form_fields ) {
		$values = array();

		foreach ( $names as $name ) {
			if ( isset( $form_data[ $name ] ) && is_array( $form_data[ $name ] ) ) {
				if ( isset( $form_fields['mf-simple-repeater'] ) ) {
					$values[] = $this->build_repeater_value( $form_data['mf-simple-repeater'], $form_fields['mf-simple-repeater'] );
				}
			} else {
				$values[] = isset( $form_data[ $name ] ) ? $form_data[ $name ] : '';
			}
		}

		return $values;
	}

	/**
	 * Flattens repeater-field submission data into a single newline-separated cell string.
	 *
	 * @param array  $repeater_data Submitted repeater rows, keyed as `{field}-{row_index}`.
	 * @param object $repeater_def  Repeater field definition with `mf_input_repeater` sub-fields.
	 * @return string Flattened repeater value.
	 */
	private function build_repeater_value( $repeater_data, $repeater_def ) {
		$fields_count   = count( $repeater_def->mf_input_repeater );
		$repeater_count = count( $repeater_data );
		$data           = '';

		for ( $i = 1; $i <= ceil( $repeater_count / $fields_count ); $i++ ) {
			for ( $k = 0; $k < $fields_count; $k++ ) {
				$input = $repeater_def->mf_input_repeater[ $k ]->mf_input_repeater_name . '-' . $i;
				$data .= ( isset( $repeater_data[ $input ] ) ? $repeater_data[ $input ] : '' ) . ' - ';
			}
			$data = trim( $data, ' - ' ) . "\n";
		}

		return $data;
	}

	/**
	 * Writes the header row (column names) to a spreadsheet or a specific tab.
	 *
	 * @param string      $sheet_id   Spreadsheet ID.
	 * @param string[]    $names      Column header names.
	 * @param string|null $sheet_title Optional tab/sheet title to scope the range to.
	 * @return array|false API response on success, false on failure.
	 */
	public function insert_names( $sheet_id, $names, $sheet_title = null ) {
		$range  = $sheet_title ? $sheet_title . '!A1:Z1' : 'A1:Z1';
		$result = $this->api->update_values( $sheet_id, $range, array( $names ) );

		if ( is_wp_error( $result ) ) {
			$this->log_error( $result );
			return false;
		}

		return $result;
	}

	/**
	 * Retrieves the currently stored/refreshed access token payload.
	 *
	 * @return object|false Decoded token object, or false on failure.
	 */
	public function token() {
		$token = $this->api->get_access_token();

		if ( is_wp_error( $token ) ) {
			$this->log_error( $token );
			return false;
		}

		$value = get_option( 'wf_google_access_token' );

		return $value ? json_decode( $value ) : false;
	}

	/**
	 * Lists all spreadsheets accessible to the authenticated account.
	 *
	 * @return string JSON-encoded Drive `files.list` response, or `{"wp_error": "..."}` on failure.
	 */
	public function get_all_spreadsheets() {
		return $this->json_result( $this->api->list_spreadsheets() );
	}

	/**
	 * Gets sheet/tab details for a given spreadsheet.
	 *
	 * @param string $spreadsheetId Spreadsheet ID.
	 * @return string JSON-encoded spreadsheet metadata, or `{"wp_error": "..."}` on failure.
	 */
	public function get_sheets_details_from_spreadsheet( $spreadsheetId ) {
		return $this->json_result( $this->api->get_spreadsheet( $spreadsheetId ) );
	}

	/**
	 * Normalizes an API result (or WP_Error) into a JSON string, logging errors along the way.
	 *
	 * @param array|\WP_Error $result Raw API client result.
	 * @return string JSON-encoded result.
	 */
	private function json_result( $result ) {
		if ( is_wp_error( $result ) ) {
			$this->log_error( $result );
			return wp_json_encode( array( 'wp_error' => $result->get_error_message() ) );
		}

		return wp_json_encode( $result );
	}

	/**
	 * Logs a WP_Error to the PHP error log in a consistent, sanitized format.
	 *
	 * @param \WP_Error $error Error to log.
	 * @return void
	 */
	private function log_error( $error ) {
		if ( is_wp_error( $error ) ) {
			error_log( sprintf( 'MetForm Google API error [%s]: %s', sanitize_key( $error->get_error_code() ), sanitize_text_field( $error->get_error_message() ) ) );
		}
	}
}