<?php
declare(strict_types=1);
namespace ConfManager\Members;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}


use ConfManager\Traits\Singleton;
use ConfManager\Security\Encryption;
use ConfManager\Security\AuditLog;

/**
 * 会員CSVインポート
 *
 * CSVファイルを読み込み、バリデーション・重複チェック後に一括登録する。
 * email_hash による重複検知で既存会員の更新にも対応。
 *
 * @since 1.0.0
 */
class MemberImport {

	use Singleton;

	/**
	 * 必須CSVカラム
	 */
	private const REQUIRED_COLUMNS = [ 'name_sei', 'name_mei', 'email' ];

	/**
	 * 許可CSVカラム
	 */
	private const ALLOWED_COLUMNS = [
		'member_no', 'member_type',
		'name_sei', 'name_mei', 'name_sei_kana', 'name_mei_kana',
		'email', 'phone',
		'org_name', 'org_dept', 'org_address',
	];

	/**
	 * 初期化
	 */
	protected function init(): void {}

	/**
	 * CSVファイルをインポートする
	 *
	 * @param string $file_path CSVファイルパス
	 * @param array  $options   {
	 *     オプション
	 *     @type bool   $update_existing 既存会員を更新するか（デフォルト: false）
	 *     @type bool   $skip_header     先頭行をヘッダーとして扱うか（デフォルト: true）
	 *     @type string $encoding        CSVのエンコーディング（デフォルト: UTF-8）
	 * }
	 * @return array{imported: int, updated: int, skipped: int, errors: array}
	 */
	public function import_csv( string $file_path, array $options = [] ): array {
		$update_existing = (bool) ( $options['update_existing'] ?? false );
		$skip_header     = (bool) ( $options['skip_header'] ?? true );
		$encoding        = $options['encoding'] ?? 'UTF-8';

		$result = [
			'imported' => 0,
			'updated'  => 0,
			'skipped'  => 0,
			'errors'   => [],
		];

		if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
			$result['errors'][] = [
				'row'     => 0,
				'message' => __( 'CSV file not found or cannot be read.', 'conferencemanager' ),
			];
			return $result;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Streaming read of admin-uploaded CSV; WP_Filesystem has no streaming reader.
		$handle = fopen( $file_path, 'r' );
		if ( false === $handle ) {
			$result['errors'][] = [
				'row'     => 0,
				'message' => __( 'Cannot open CSV file.', 'conferencemanager' ),
			];
			return $result;
		}

		// ヘッダー行の読み込み
		$header = fgetcsv( $handle, null, ',', '"', '' );
		if ( false === $header || null === $header ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Matches fopen() above.
			fclose( $handle );
			$result['errors'][] = [
				'row'     => 1,
				'message' => __( 'Failed to read CSV header.', 'conferencemanager' ),
			];
			return $result;
		}

		// BOMを除去
		if ( isset( $header[0] ) ) {
			$header[0] = preg_replace( '/^\x{FEFF}/u', '', $header[0] );
		}

		// エンコーディング変換
		if ( 'UTF-8' !== strtoupper( $encoding ) ) {
			$header = array_map( function ( $col ) use ( $encoding ) {
				return mb_convert_encoding( $col, 'UTF-8', $encoding );
			}, $header );
		}

		$header = array_map( 'trim', $header );
		$header = array_map( 'strtolower', $header );

		// ヘッダーバリデーション
		$header_error = $this->validate_header( $header );
		if ( null !== $header_error ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Matches fopen() above.
			fclose( $handle );
			$result['errors'][] = [
				'row'     => 1,
				'message' => $header_error,
			];
			return $result;
		}

		$manager = MemberManager::instance();
		$row_num = 1; // ヘッダー行が1行目

		while ( false !== ( $csv_row = fgetcsv( $handle, null, ',', '"', '' ) ) ) {
			$row_num++;

			if ( null === $csv_row || [ null ] === $csv_row ) {
				continue; // 空行スキップ
			}

			// エンコーディング変換
			if ( 'UTF-8' !== strtoupper( $encoding ) ) {
				$csv_row = array_map( function ( $val ) use ( $encoding ) {
					return mb_convert_encoding( $val, 'UTF-8', $encoding );
				}, $csv_row );
			}

			// ヘッダーとカラム数が一致しない場合
			if ( count( $csv_row ) !== count( $header ) ) {
				$result['errors'][] = [
					'row'     => $row_num,
					'message' => __( 'Column count does not match header.', 'conferencemanager' ),
				];
				continue;
			}

			$data = array_combine( $header, $csv_row );
			$data = array_map( 'trim', $data );

			// 許可カラムのみに絞る
			$data = array_intersect_key( $data, array_flip( self::ALLOWED_COLUMNS ) );

			// 必須チェック
			foreach ( self::REQUIRED_COLUMNS as $col ) {
				if ( empty( $data[ $col ] ) ) {
					$result['errors'][] = [
						'row'     => $row_num,
						'message' => sprintf(
							/* translators: %s: column name */
							__( 'Required field "%s" is empty.', 'conferencemanager' ),
							$col
						),
					];
					continue 2;
				}
			}

			// メールバリデーション
			if ( ! is_email( $data['email'] ) ) {
				$result['errors'][] = [
					'row'     => $row_num,
					'message' => __( 'Invalid email address.', 'conferencemanager' ),
				];
				continue;
			}

			// 重複チェック
			$existing = $manager->get_by_email( $data['email'] );

			if ( null !== $existing ) {
				if ( $update_existing ) {
					// 🔴 空欄のセルを既存値の削除として扱わない。
					//    MemberManager::update() は isset() で判定するので、キーが在れば
					//    空文字も「消せ」と解釈して NULL を書く（会員編集画面からは
					//    それが正しい操作）。しかし CSV はヘッダーに在る列を空文字ごと
					//    渡すため、電話番号・住所・会員番号の空欄が既存の値を潰していた。
					//    CSV で「値を消す」運用は実在しない（消すなら会員編集画面で 1 件ずつ）
					//    のに対し、「名簿の一部の列だけ補完したい」は常時ある。
					//    壊れる方向が非対称なので安全側に倒す。
					//    ⚠️ 新規作成（create()）側は触らない（空を NULL にするのが正しい）。
					$update_payload = array_filter( $data, static fn( $v ) => '' !== $v );
					$update_result  = $manager->update( (int) $existing['id'], $update_payload );
					if ( is_wp_error( $update_result ) ) {
						$result['errors'][] = [
							'row'     => $row_num,
							'message' => $update_result->get_error_message(),
						];
					} else {
						$result['updated']++;
					}
				} else {
					$result['skipped']++;
				}
				continue;
			}

			// 新規作成
			$create_result = $manager->create( $data );
			if ( is_wp_error( $create_result ) ) {
				$result['errors'][] = [
					'row'     => $row_num,
					'message' => $create_result->get_error_message(),
				];
			} else {
				$result['imported']++;
			}
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Matches fopen() above.
		fclose( $handle );

		AuditLog::instance()->log( 'import_members', 'member', null, [
			'file'     => basename( $file_path ),
			'imported' => $result['imported'],
			'updated'  => $result['updated'],
			'skipped'  => $result['skipped'],
			'errors'   => count( $result['errors'] ),
		] );

		return $result;
	}

	/**
	 * CSVヘッダーのバリデーション
	 *
	 * @param array $header ヘッダー配列
	 * @return string|null エラーメッセージ、または null（OK）
	 */
	private function validate_header( array $header ): ?string {
		$missing = array_diff( self::REQUIRED_COLUMNS, $header );
		if ( ! empty( $missing ) ) {
			return sprintf(
				/* translators: %s: column names */
				__( 'Required column is missing: %s', 'conferencemanager' ),
				implode( ', ', $missing )
			);
		}

		return null;
	}

	/**
	 * CSVテンプレートのヘッダー行を返す
	 *
	 * @return array
	 */
	public static function get_template_header(): array {
		return self::ALLOWED_COLUMNS;
	}
}
