<?php
/**
 * Multi-connection storage helpers for Email Delivery.
 *
 * @package EmailSmtp
 */

namespace EmailSmtp\Includes;

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

final class ConnectionStore {

	/** Keys copied from a connection into the flat runtime slice. */
	private const CONNECTION_META = [
		'id',
		'name',
		'provider',
		'transport',
		'from_email',
		'from_name',
		'reply_to_email',
		'reply_to_name',
		'return_path',
		'set_return_path',
		'force_from_email',
		'force_from_name',
		'override_from',
	];

	/**
	 * Ensure connections exist (migrate from flat v1) and sync flat aliases from the default connection.
	 *
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function ensure( array $settings ): array {
		$connections = isset( $settings['connections'] ) && is_array( $settings['connections'] )
			? $settings['connections']
			: [];

		// Migrate legacy flat settings once. Do not recreate a connection after the user deletes the last one.
		$migration_version = (int) ( $settings['migration_version'] ?? 0 );
		if (
			$connections === []
			&& $migration_version < SettingsSchema::MIGRATION_VERSION
			&& self::should_migrate_flat( $settings )
		) {
			$settings    = self::migrate_flat_to_connections( $settings );
			$connections = is_array( $settings['connections'] ?? null ) ? $settings['connections'] : [];
		}

		$normalized = [];
		foreach ( $connections as $id => $conn ) {
			if ( ! is_array( $conn ) ) {
				continue;
			}
			$cid = sanitize_key( (string) ( $conn['id'] ?? $id ) );
			if ( $cid === '' ) {
				continue;
			}
			$conn['id'] = $cid;
			$conn['name'] = sanitize_text_field( (string) ( $conn['name'] ?? $cid ) );
			$provider = sanitize_key( (string) ( $conn['provider'] ?? 'wordpress' ) );
			if ( ! in_array( $provider, ProviderRegistry::ids(), true ) ) {
				$provider = 'wordpress';
			}
			$conn['provider'] = $provider;
			$transport = sanitize_key( (string) ( $conn['transport'] ?? 'api' ) );
			if ( $provider === 'smtp' ) {
				$transport = 'smtp';
			} elseif ( $provider === 'wordpress' || $provider === 'google' || $provider === 'microsoft' ) {
				$transport = 'api';
			} elseif ( ! in_array( $transport, [ 'api', 'smtp' ], true ) ) {
				$transport = 'api';
			} elseif ( $transport === 'smtp' && ! SmtpRelayProfiles::supports( $provider ) ) {
				$transport = 'api';
			}
			$conn['transport'] = $transport;
			$from = sanitize_email( (string) ( $conn['from_email'] ?? '' ) );
			$conn['from_email'] = is_email( $from ) ? $from : sanitize_email( (string) get_option( 'admin_email' ) );
			$conn['from_name'] = sanitize_text_field( (string) ( $conn['from_name'] ?? get_option( 'blogname' ) ) );
			$conn['force_from_email'] = ! empty( $conn['force_from_email'] ) && (string) $conn['force_from_email'] !== '0' ? '1' : '0';
			$conn['force_from_name'] = ! empty( $conn['force_from_name'] ) && (string) $conn['force_from_name'] !== '0' ? '1' : '0';
			$conn['set_return_path'] = ! isset( $conn['set_return_path'] ) || ( ! empty( $conn['set_return_path'] ) && (string) $conn['set_return_path'] !== '0' ) ? '1' : '0';
			$conn['override_from'] = $conn['force_from_email'];
			$conn['priority'] = max( 0, min( 999, (int) ( $conn['priority'] ?? 100 ) ) );
			$normalized[ $cid ] = $conn;
		}
		$settings['connections'] = $normalized;

		$default_id = sanitize_key( (string) ( $settings['default_connection_id'] ?? '' ) );
		if ( $default_id === '' || ! isset( $normalized[ $default_id ] ) ) {
			$default_id = $normalized !== [] ? (string) array_key_first( $normalized ) : '';
		}
		$settings['default_connection_id'] = $default_id;

		$fallback_id = sanitize_key( (string) ( $settings['fallback_connection_id'] ?? '' ) );
		if ( $fallback_id !== '' && ( ! isset( $normalized[ $fallback_id ] ) || $fallback_id === $default_id ) ) {
			$fallback_id = '';
		}
		$settings['fallback_connection_id'] = $fallback_id;
		if ( $fallback_id === '' ) {
			$settings['fallback_enabled'] = '0';
		} elseif ( empty( $settings['fallback_enabled'] ) || (string) $settings['fallback_enabled'] === '0' ) {
			// Keep explicit off; UI sets both.
		}

		if ( $default_id !== '' && isset( $normalized[ $default_id ] ) ) {
			$settings = self::sync_flat_from_connection( $settings, $normalized[ $default_id ] );
		}

		// Alias fallback_provider for older orchestrator paths.
		if ( $fallback_id !== '' && isset( $normalized[ $fallback_id ] ) ) {
			$settings['fallback_provider'] = (string) ( $normalized[ $fallback_id ]['provider'] ?? '' );
		}

		return $settings;
	}

	/**
	 * True when flat settings look like a pre-multi-connection configuration worth migrating.
	 *
	 * @param array<string, mixed> $settings
	 */
	private static function should_migrate_flat( array $settings ): bool {
		$provider = sanitize_key( (string) ( $settings['provider'] ?? 'wordpress' ) );
		if ( $provider !== '' && $provider !== 'wordpress' && in_array( $provider, ProviderRegistry::ids(), true ) ) {
			return true;
		}
		if ( trim( (string) ( $settings['smtp_host'] ?? '' ) ) !== '' ) {
			return true;
		}
		foreach ( CredentialManager::secret_keys() as $key ) {
			if ( trim( (string) ( $settings[ $key ] ?? '' ) ) !== '' ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function migrate_flat_to_connections( array $settings ): array {
		$provider = sanitize_key( (string) ( $settings['provider'] ?? 'wordpress' ) );
		if ( ! in_array( $provider, ProviderRegistry::ids(), true ) ) {
			$provider = 'wordpress';
		}

		$id   = 'conn_primary';
		$name = ProviderRegistry::get( $provider )?->get_name() ?? __( 'Primary', 'wp-powersuite' );
		$bag  = SettingsSchema::provider_bag( $settings, $provider );

		$conn = array_merge(
			$bag,
			[
				'id'               => $id,
				'name'             => $name,
				'provider'         => $provider,
				'from_email'       => (string) ( $settings['from_email'] ?? '' ),
				'from_name'        => (string) ( $settings['from_name'] ?? '' ),
				'reply_to_email'   => (string) ( $settings['reply_to_email'] ?? '' ),
				'reply_to_name'    => (string) ( $settings['reply_to_name'] ?? '' ),
				'return_path'      => (string) ( $settings['return_path'] ?? '' ),
				'set_return_path'  => (string) ( $settings['set_return_path'] ?? '1' ),
				'force_from_email' => (string) ( $settings['force_from_email'] ?? '1' ),
				'force_from_name'  => (string) ( $settings['force_from_name'] ?? '1' ),
				'override_from'    => (string) ( $settings['force_from_email'] ?? '1' ),
			]
		);

		$connections = [ $id => $conn ];
		$settings['default_connection_id'] = $id;

		$fallback_type = sanitize_key( (string) ( $settings['fallback_provider'] ?? '' ) );
		if (
			! empty( $settings['fallback_enabled'] )
			&& (string) $settings['fallback_enabled'] !== '0'
			&& $fallback_type !== ''
			&& $fallback_type !== $provider
			&& in_array( $fallback_type, ProviderRegistry::ids(), true )
			&& $fallback_type !== 'wordpress'
		) {
			$fb_id   = 'conn_fallback';
			$fb_name = ProviderRegistry::get( $fallback_type )?->get_name() ?? __( 'Fallback', 'wp-powersuite' );
			$fb_bag  = SettingsSchema::provider_bag( $settings, $fallback_type );
			$connections[ $fb_id ] = array_merge(
				$fb_bag,
				[
					'id'               => $fb_id,
					'name'             => $fb_name . ' (' . __( 'Fallback', 'wp-powersuite' ) . ')',
					'provider'         => $fallback_type,
					'from_email'       => (string) ( $settings['from_email'] ?? '' ),
					'from_name'        => (string) ( $settings['from_name'] ?? '' ),
					'set_return_path'  => '1',
					'force_from_email' => '1',
					'force_from_name'  => '1',
					'override_from'    => '1',
				]
			);
			$settings['fallback_connection_id'] = $fb_id;
		} else {
			$settings['fallback_connection_id'] = '';
		}

		$settings['connections'] = $connections;
		$settings['_migration_dirty'] = true;

		return $settings;
	}

	/**
	 * Flat settings slice used by providers / sender_bag for one connection.
	 *
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function settings_for_connection( array $settings, string $connection_id ): array {
		$settings = self::ensure( $settings );
		$conn     = $settings['connections'][ $connection_id ] ?? null;
		if ( ! is_array( $conn ) ) {
			return $settings;
		}

		$flat = $settings;
		foreach ( self::CONNECTION_META as $key ) {
			if ( array_key_exists( $key, $conn ) ) {
				$flat[ $key ] = $conn[ $key ];
			}
		}
		$provider = (string) ( $conn['provider'] ?? 'wordpress' );
		$flat['provider'] = $provider;
		foreach ( SettingsSchema::provider_bag( $conn, $provider ) as $k => $v ) {
			$flat[ $k ] = $v;
		}
		// Also copy any extra credential keys present on the connection.
		foreach ( $conn as $k => $v ) {
			if ( in_array( $k, self::CONNECTION_META, true ) ) {
				continue;
			}
			$flat[ $k ] = $v;
		}

		return $flat;
	}

	/**
	 * @param array<string, mixed> $settings
	 * @param array<string, mixed> $conn
	 * @return array<string, mixed>
	 */
	public static function sync_flat_from_connection( array $settings, array $conn ): array {
		$provider = (string) ( $conn['provider'] ?? 'wordpress' );
		$settings['provider'] = $provider;
		foreach ( [ 'from_email', 'from_name', 'reply_to_email', 'reply_to_name', 'return_path', 'set_return_path', 'force_from_email', 'force_from_name', 'override_from' ] as $key ) {
			if ( array_key_exists( $key, $conn ) ) {
				$settings[ $key ] = $conn[ $key ];
			}
		}
		foreach ( SettingsSchema::provider_bag( $conn, $provider ) as $k => $v ) {
			$settings[ $k ] = $v;
		}
		foreach ( $conn as $k => $v ) {
			if ( in_array( $k, self::CONNECTION_META, true ) ) {
				continue;
			}
			$settings[ $k ] = $v;
		}

		return $settings;
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, array<string, mixed>>
	 */
	public static function all( array $settings ): array {
		$settings = self::ensure( $settings );
		return is_array( $settings['connections'] ?? null ) ? $settings['connections'] : [];
	}

	/**
	 * Merge posted credentials into a connection, preserving masked secrets.
	 *
	 * @param array<string, mixed> $existing_conn
	 * @param array<string, mixed> $posted
	 * @return array<string, mixed>
	 */
	public static function merge_connection_secrets( array $existing_conn, array $posted ): array {
		$merged = array_merge( $existing_conn, $posted );
		return CredentialManager::merge_secrets( $merged, $existing_conn, CredentialManager::secret_keys() );
	}

	public static function new_id(): string {
		return 'conn_' . strtolower( wp_generate_password( 8, false, false ) );
	}

	/**
	 * Find connection ID whose From email matches (case-insensitive).
	 * Falls back to the default connection when none match.
	 *
	 * @param array<string, mixed> $settings
	 */
	public static function resolve_connection_id( array $settings, string $from_email ): string {
		$settings    = self::ensure( $settings );
		$connections = is_array( $settings['connections'] ?? null ) ? $settings['connections'] : [];
		$needle      = strtolower( sanitize_email( $from_email ) );

		if ( $needle !== '' && is_email( $needle ) ) {
			foreach ( $connections as $id => $conn ) {
				if ( ! is_array( $conn ) ) {
					continue;
				}
				$candidate = strtolower( sanitize_email( (string) ( $conn['from_email'] ?? '' ) ) );
				if ( $candidate !== '' && $candidate === $needle ) {
					return sanitize_key( (string) $id );
				}
			}
		}

		$default = sanitize_key( (string) ( $settings['default_connection_id'] ?? '' ) );
		if ( $default !== '' && isset( $connections[ $default ] ) ) {
			return $default;
		}

		return $connections !== [] ? sanitize_key( (string) array_key_first( $connections ) ) : '';
	}

	/**
	 * Why a connection was selected for this From address.
	 *
	 * @param array<string, mixed> $settings
	 */
	public static function routing_reason( array $settings, string $from_email, string $connection_id = '' ): string {
		$settings      = self::ensure( $settings );
		$connections   = is_array( $settings['connections'] ?? null ) ? $settings['connections'] : [];
		$connection_id = sanitize_key( $connection_id !== '' ? $connection_id : self::resolve_connection_id( $settings, $from_email ) );
		$needle        = strtolower( sanitize_email( $from_email ) );
		$default       = sanitize_key( (string) ( $settings['default_connection_id'] ?? '' ) );

		if ( $connection_id === '' ) {
			return 'none';
		}

		if ( $needle !== '' && is_email( $needle ) && isset( $connections[ $connection_id ] ) && is_array( $connections[ $connection_id ] ) ) {
			$candidate = strtolower( sanitize_email( (string) ( $connections[ $connection_id ]['from_email'] ?? '' ) ) );
			if ( $candidate !== '' && $candidate === $needle ) {
				return 'from_match';
			}
		}

		if ( $connection_id === $default ) {
			return 'primary';
		}

		return 'default';
	}

	/**
	 * Flat settings for the connection matching a From email (or default).
	 *
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function settings_for_from( array $settings, string $from_email ): array {
		$id = self::resolve_connection_id( $settings, $from_email );
		if ( $id === '' ) {
			return self::ensure( $settings );
		}
		return self::settings_for_connection( $settings, $id );
	}

	/**
	 * Whether another connection already uses this From email.
	 *
	 * @param array<string, mixed> $settings
	 */
	public static function from_email_in_use( array $settings, string $from_email, string $exclude_connection_id = '' ): bool {
		$settings    = self::ensure( $settings );
		$connections = is_array( $settings['connections'] ?? null ) ? $settings['connections'] : [];
		$needle      = strtolower( sanitize_email( $from_email ) );
		if ( $needle === '' || ! is_email( $needle ) ) {
			return false;
		}
		$exclude = sanitize_key( $exclude_connection_id );

		foreach ( $connections as $id => $conn ) {
			if ( ! is_array( $conn ) ) {
				continue;
			}
			if ( $exclude !== '' && sanitize_key( (string) $id ) === $exclude ) {
				continue;
			}
			$candidate = strtolower( sanitize_email( (string) ( $conn['from_email'] ?? '' ) ) );
			if ( $candidate === $needle ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * URL to a provider icon asset (prefer SVG brand marks, then PNG).
	 */
	public static function provider_icon_url( string $provider_id ): string {
		$provider_id = sanitize_key( $provider_id );
		$base        = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		$dir         = defined( 'WP_POWERSUITE_PLUGIN_DIR' )
			? WP_POWERSUITE_PLUGIN_DIR . 'modules/email-smtp/assets/providers/'
			: dirname( __DIR__ ) . '/assets/providers/';

		foreach ( [ 'svg', 'png' ] as $ext ) {
			$file = $dir . $provider_id . '.' . $ext;
			if ( is_readable( $file ) && filesize( $file ) > 100 ) {
				return $base . 'modules/email-smtp/assets/providers/' . $provider_id . '.' . $ext;
			}
		}

		return '';
	}
}
