# Integration Patterns and Communication - Centron Enterprise Application

## Overview

This document provides comprehensive coverage of integration patterns and implementations within the Centron .NET 8 enterprise application. Integration patterns encompass external API integration, data transformation, error handling, retry mechanisms, circuit breakers, fault tolerance, message patterns, and service orchestration that enable seamless communication between systems.

## Integration Architecture

### **Multi-Channel Integration Strategy**

The Centron application supports multiple integration channels and patterns:

1. **REST API Integration**: HTTP-based services for modern web APIs
2. **SOAP Web Services**: Legacy system integration and enterprise services
3. **File-based Integration**: EDI, CSV, and XML file processing
4. **Database Integration**: Direct database connections and synchronization
5. **Message Queue Integration**: Asynchronous messaging and event processing
6. **Email Integration**: SMTP and email processing services

## External API Integration Patterns

### **1. REST Client Base Pattern**

The foundation for all external REST API integrations:

```csharp
public abstract class RestClientBase
{
    protected readonly HttpClient _httpClient;
    protected readonly ILogger _logger;
    protected readonly RestClientCredentials _credentials;
    protected AccessToken _accessToken;

    protected RestClientBase(string baseUrl, RestClientCredentials credentials)
    {
        _credentials = credentials ?? throw new ArgumentNullException(nameof(credentials));
        _logger = LogManager.GetCurrentClassLogger();

        _httpClient = new HttpClient(new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = ValidateCertificate
        })
        {
            BaseAddress = new Uri(baseUrl),
            Timeout = TimeSpan.FromSeconds(30)
        };

        // Set default headers
        _httpClient.DefaultRequestHeaders.Accept.Clear();
        _httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        _httpClient.DefaultRequestHeaders.Add("User-Agent", "CentronClient/1.0");
    }

    protected async Task<Result<T>> SendRequestAsync<T>(
        HttpRequestMessage request,
        CancellationToken cancellationToken = default) where T : class
    {
        try
        {
            var response = await _httpClient.SendAsync(request, cancellationToken);
            return await ProcessResponseAsync<T>(response);
        }
        catch (HttpRequestException ex)
        {
            _logger.Error(ex, "HTTP request failed: {Method} {Url}", request.Method, request.RequestUri);
            return Result<T>.AsError($"HTTP request failed: {ex.Message}");
        }
        catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
        {
            _logger.Error(ex, "Request timeout: {Method} {Url}", request.Method, request.RequestUri);
            return Result<T>.AsError("Request timeout");
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Unexpected error during request: {Method} {Url}", request.Method, request.RequestUri);
            return Result<T>.AsError($"Request failed: {ex.Message}");
        }
    }

    protected async Task<Result<T>> ProcessResponseAsync<T>(HttpResponseMessage response) where T : class
    {
        var responseContent = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
        {
            var error = ParseErrorResponse(responseContent, response.StatusCode);
            _logger.Error("API request failed: {StatusCode} - {Error}", response.StatusCode, error);
            return Result<T>.AsError($"API Error ({response.StatusCode}): {error}");
        }

        try
        {
            if (typeof(T) == typeof(string))
            {
                return Result<T>.AsSuccess(responseContent as T);
            }

            var deserializedObject = JsonSerializer.Deserialize<T>(responseContent, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            });

            return Result<T>.AsSuccess(deserializedObject);
        }
        catch (JsonException ex)
        {
            _logger.Error(ex, "Failed to deserialize response: {Content}", responseContent);
            return Result<T>.AsError($"Response deserialization failed: {ex.Message}");
        }
    }

    protected async Task<Result<AccessToken>> GetJsonWebTokenAsync(string tokenEndpoint)
    {
        try
        {
            var tokenRequest = CreateTokenRequest();
            var response = await _httpClient.SendAsync(tokenRequest);

            if (!response.IsSuccessStatusCode)
            {
                var error = await response.Content.ReadAsStringAsync();
                return Result<AccessToken>.AsError($"Token request failed: {error}");
            }

            var tokenResponse = await response.Content.ReadAsStringAsync();
            var token = JsonSerializer.Deserialize<AccessToken>(tokenResponse);

            return Result<AccessToken>.AsSuccess(token);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Token acquisition failed");
            return Result<AccessToken>.AsError($"Token acquisition failed: {ex.Message}");
        }
    }

    private HttpRequestMessage CreateTokenRequest()
    {
        var request = new HttpRequestMessage(HttpMethod.Post, "/oauth/token");

        var parameters = new Dictionary<string, string>
        {
            {"grant_type", "client_credentials"},
            {"client_id", _credentials.ClientId},
            {"client_secret", _credentials.ClientSecret}
        };

        if (!string.IsNullOrEmpty(_credentials.Scope))
            parameters["scope"] = _credentials.Scope;

        request.Content = new FormUrlEncodedContent(parameters);
        return request;
    }

    private bool ValidateCertificate(HttpRequestMessage request, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
    {
        // Implement certificate validation logic based on environment
        if (_credentials.AllowInvalidCertificates)
            return true;

        return errors == SslPolicyErrors.None;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _httpClient?.Dispose();
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

// Specialized external API client implementation
public class FinApiClient : RestClientBase, IFinApiClient
{
    private readonly IOnlineBankingConnectionDialog _dialogHandler;
    private readonly bool _sandBoxMode;

    public FinApiClient(RestClientCredentials clientCredentials, bool sandBoxMode = false, IOnlineBankingConnectionDialog dialogHandler = null)
        : base(sandBoxMode ? FinApiConstants.SandboxAccessApiUrl : FinApiConstants.LiveAccessApiUrl, clientCredentials)
    {
        _dialogHandler = dialogHandler;
        _sandBoxMode = sandBoxMode;
    }

    public async Task<Result<User>> GetUserAccountAsync()
    {
        if (_accessToken == null || _accessToken.IsExpired())
            return Result<User>.AsError("No valid access token!");

        _dialogHandler?.DisplayInfoMessage("Loading account data for the authorized user...");

        var request = new HttpRequestMessage(HttpMethod.Get, FinApiConstants.UserAccountPath);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken.Token);

        var response = await SendRequestAsync<User>(request);

        _dialogHandler?.DisplayInfoMessage($"Response: {response.Status}");

        return response;
    }

    public async Task<Result> CreateUserAccountAsync(CreateUserAccountRequest createRequest)
    {
        try
        {
            // Get client token for user creation
            var tokenResult = await GetJsonWebTokenAsync(FinApiConstants.TokenPath);
            if (tokenResult.Status != ResultStatus.Success)
                return Result.FromResult(tokenResult);

            _dialogHandler?.DisplayInfoMessage("Creating user account...");

            var request = new HttpRequestMessage(HttpMethod.Post, FinApiConstants.UserAccountPath);
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Data.Token);
            request.Content = new StringContent(
                JsonSerializer.Serialize(createRequest),
                Encoding.UTF8,
                "application/json");

            var response = await _httpClient.SendAsync(request);
            var responseContent = await response.Content.ReadAsStringAsync();

            if (response.StatusCode != HttpStatusCode.Created)
            {
                return Result.AsError($"User creation failed: {responseContent}");
            }

            _dialogHandler?.DisplayInfoMessage("User account created successfully");
            return Result.AsSuccess();
        }
        catch (Exception ex)
        {
            return Result.AsError($"User account creation failed: {ex.Message}");
        }
    }
}
```

### **2. Circuit Breaker Pattern for External Services**

```csharp
public class CircuitBreaker
{
    private readonly string _name;
    private readonly int _failureThreshold;
    private readonly TimeSpan _timeout;
    private readonly TimeSpan _recoveryTimeout;
    private readonly ILogger _logger;

    private CircuitBreakerState _state = CircuitBreakerState.Closed;
    private int _failureCount = 0;
    private DateTime _lastFailureTime = DateTime.MinValue;
    private readonly object _lock = new object();

    public CircuitBreaker(string name, int failureThreshold, TimeSpan timeout, TimeSpan recoveryTimeout)
    {
        _name = name;
        _failureThreshold = failureThreshold;
        _timeout = timeout;
        _recoveryTimeout = recoveryTimeout;
        _logger = LogManager.GetCurrentClassLogger();
    }

    public async Task<Result<T>> ExecuteAsync<T>(Func<Task<Result<T>>> operation)
    {
        if (_state == CircuitBreakerState.Open)
        {
            if (DateTime.UtcNow - _lastFailureTime < _recoveryTimeout)
            {
                _logger.Warn("Circuit breaker {Name} is OPEN - rejecting call", _name);
                return Result<T>.AsError("Circuit breaker is open");
            }

            // Transition to Half-Open state
            lock (_lock)
            {
                if (_state == CircuitBreakerState.Open)
                {
                    _state = CircuitBreakerState.HalfOpen;
                    _logger.Info("Circuit breaker {Name} transitioning to HALF-OPEN", _name);
                }
            }
        }

        try
        {
            var result = await operation();

            if (result.Status == ResultStatus.Success)
            {
                OnSuccess();
            }
            else
            {
                OnFailure();
            }

            return result;
        }
        catch (Exception ex)
        {
            OnFailure();
            return Result<T>.AsError($"Operation failed: {ex.Message}");
        }
    }

    private void OnSuccess()
    {
        lock (_lock)
        {
            _failureCount = 0;
            if (_state == CircuitBreakerState.HalfOpen)
            {
                _state = CircuitBreakerState.Closed;
                _logger.Info("Circuit breaker {Name} transitioning to CLOSED", _name);
            }
        }
    }

    private void OnFailure()
    {
        lock (_lock)
        {
            _failureCount++;
            _lastFailureTime = DateTime.UtcNow;

            if (_failureCount >= _failureThreshold && _state != CircuitBreakerState.Open)
            {
                _state = CircuitBreakerState.Open;
                _logger.Warn("Circuit breaker {Name} transitioning to OPEN after {FailureCount} failures", _name, _failureCount);
            }
        }
    }

    public CircuitBreakerState State => _state;
    public int FailureCount => _failureCount;
}

public enum CircuitBreakerState
{
    Closed,
    Open,
    HalfOpen
}

// Circuit breaker integration with external service client
public class ResilientExternalServiceClient
{
    private readonly IExternalServiceClient _client;
    private readonly CircuitBreaker _circuitBreaker;
    private readonly IRetryPolicy _retryPolicy;

    public ResilientExternalServiceClient(IExternalServiceClient client)
    {
        _client = client;
        _circuitBreaker = new CircuitBreaker(
            name: "ExternalService",
            failureThreshold: 5,
            timeout: TimeSpan.FromSeconds(30),
            recoveryTimeout: TimeSpan.FromMinutes(1));

        _retryPolicy = new ExponentialRetryPolicy(
            maxRetries: 3,
            baseDelay: TimeSpan.FromSeconds(1));
    }

    public async Task<Result<T>> CallServiceAsync<T>(Func<Task<Result<T>>> serviceCall)
    {
        return await _circuitBreaker.ExecuteAsync(async () =>
        {
            return await _retryPolicy.ExecuteAsync(serviceCall);
        });
    }
}
```

### **3. Retry Policy Pattern**

```csharp
public interface IRetryPolicy
{
    Task<Result<T>> ExecuteAsync<T>(Func<Task<Result<T>>> operation);
}

public class ExponentialRetryPolicy : IRetryPolicy
{
    private readonly int _maxRetries;
    private readonly TimeSpan _baseDelay;
    private readonly double _multiplier;
    private readonly TimeSpan _maxDelay;
    private readonly ILogger _logger;

    public ExponentialRetryPolicy(
        int maxRetries = 3,
        TimeSpan? baseDelay = null,
        double multiplier = 2.0,
        TimeSpan? maxDelay = null)
    {
        _maxRetries = maxRetries;
        _baseDelay = baseDelay ?? TimeSpan.FromSeconds(1);
        _multiplier = multiplier;
        _maxDelay = maxDelay ?? TimeSpan.FromMinutes(1);
        _logger = LogManager.GetCurrentClassLogger();
    }

    public async Task<Result<T>> ExecuteAsync<T>(Func<Task<Result<T>>> operation)
    {
        var attempt = 0;
        var delay = _baseDelay;

        while (attempt <= _maxRetries)
        {
            try
            {
                var result = await operation();

                if (result.Status == ResultStatus.Success || !IsRetriableError(result))
                {
                    if (attempt > 0)
                    {
                        _logger.Info("Operation succeeded after {Attempts} attempts", attempt + 1);
                    }
                    return result;
                }

                if (attempt < _maxRetries)
                {
                    _logger.Warn("Operation failed (attempt {Attempt}/{MaxAttempts}): {Error}. Retrying in {Delay}ms",
                        attempt + 1, _maxRetries + 1, result.Error, delay.TotalMilliseconds);

                    await Task.Delay(delay);
                    delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * _multiplier, _maxDelay.TotalMilliseconds));
                }

                attempt++;
            }
            catch (Exception ex)
            {
                if (attempt < _maxRetries && IsRetriableException(ex))
                {
                    _logger.Warn(ex, "Operation threw exception (attempt {Attempt}/{MaxAttempts}). Retrying in {Delay}ms",
                        attempt + 1, _maxRetries + 1, delay.TotalMilliseconds);

                    await Task.Delay(delay);
                    delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * _multiplier, _maxDelay.TotalMilliseconds));
                    attempt++;
                }
                else
                {
                    return Result<T>.AsError($"Operation failed after {attempt + 1} attempts: {ex.Message}");
                }
            }
        }

        _logger.Error("Operation failed after {MaxAttempts} attempts", _maxRetries + 1);
        return Result<T>.AsError($"Operation failed after {_maxRetries + 1} attempts");
    }

    private bool IsRetriableError(Result result)
    {
        // Define which errors are worth retrying
        if (result.Error == null) return false;

        return result.Error.Contains("timeout") ||
               result.Error.Contains("connection") ||
               result.Error.Contains("network") ||
               result.Error.Contains("503") ||
               result.Error.Contains("502") ||
               result.Error.Contains("504");
    }

    private bool IsRetriableException(Exception exception)
    {
        return exception is TimeoutException ||
               exception is HttpRequestException ||
               exception is SocketException ||
               (exception is TaskCanceledException && exception.InnerException is TimeoutException);
    }
}

// Polly-style retry policy for more advanced scenarios
public class AdvancedRetryPolicy : IRetryPolicy
{
    public async Task<Result<T>> ExecuteAsync<T>(Func<Task<Result<T>>> operation)
    {
        var retryPolicy = Policy
            .Handle<HttpRequestException>()
            .Or<TimeoutException>()
            .Or<TaskCanceledException>()
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    LogManager.GetCurrentClassLogger().Warn(
                        "Retry {RetryCount} in {Delay}ms due to: {Exception}",
                        retryCount, timespan.TotalMilliseconds, outcome.Exception?.Message);
                });

        try
        {
            var result = await retryPolicy.ExecuteAsync(operation);
            return result;
        }
        catch (Exception ex)
        {
            return Result<T>.AsError($"All retry attempts failed: {ex.Message}");
        }
    }
}
```

## Data Transformation and Mapping Patterns

### **1. Entity-DTO Conversion Pattern**

```csharp
public static class ObjectMapper
{
    private static readonly IMapper _mapper;

    static ObjectMapper()
    {
        var configuration = new MapperConfiguration(cfg =>
        {
            // Account mappings
            cfg.CreateMap<Account, AccountDTO>()
                .ForMember(dest => dest.ContactPersons, opt => opt.MapFrom(src => src.ContactPersons))
                .ForMember(dest => dest.Addresses, opt => opt.MapFrom(src => src.Addresses));

            cfg.CreateMap<AccountDTO, Account>()
                .ForMember(dest => dest.ContactPersons, opt => opt.MapFrom(src => src.ContactPersons))
                .ForMember(dest => dest.Addresses, opt => opt.MapFrom(src => src.Addresses));

            // Custom mappings with transformations
            cfg.CreateMap<Account, AccountSummaryDTO>()
                .ForMember(dest => dest.TotalContracts, opt => opt.MapFrom(src => src.Contracts.Count))
                .ForMember(dest => dest.ActiveContracts, opt => opt.MapFrom(src => src.Contracts.Count(c => c.IsActive)))
                .ForMember(dest => dest.LastActivity, opt => opt.MapFrom(src => src.LastModified));

            // Complex nested mappings
            cfg.CreateMap<Receipt, ReceiptDTO>()
                .ForMember(dest => dest.Items, opt => opt.MapFrom(src => src.ReceiptItems))
                .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.CompanyName))
                .ForMember(dest => dest.PaymentMethodName, opt => opt.MapFrom(src => src.PaymentMethod.Name));
        });

        configuration.AssertConfigurationIsValid();
        _mapper = configuration.CreateMapper();
    }

    public static TDestination Map<TDestination>(object source)
    {
        return _mapper.Map<TDestination>(source);
    }

    public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
    {
        return _mapper.Map(source, destination);
    }

    public static IEnumerable<TDestination> Map<TDestination>(IEnumerable source)
    {
        return _mapper.Map<IEnumerable<TDestination>>(source);
    }
}

// Custom conversion methods for complex transformations
public static class ConversionExtensions
{
    public static Account ConvertAccountDTOToAccount(AccountDTO accountDTO)
    {
        if (accountDTO == null) return null;

        var account = ObjectMapper.Map<Account>(accountDTO);

        // Custom logic that AutoMapper can't handle
        if (accountDTO.CustomFields != null)
        {
            account.CustomProperties = accountDTO.CustomFields
                .Select(cf => new CustomProperty
                {
                    Name = cf.Key,
                    Value = cf.Value,
                    DataType = InferDataType(cf.Value)
                })
                .ToList();
        }

        // Handle special business logic
        if (account.CustomerType == CustomerType.Business && string.IsNullOrEmpty(account.VatNumber))
        {
            throw new ValidationException("VAT number is required for business customers");
        }

        return account;
    }

    public static AccountDTO ConvertAccountToAccountDTO(Account account)
    {
        if (account == null) return null;

        var accountDTO = ObjectMapper.Map<AccountDTO>(account);

        // Convert custom properties back to dictionary
        if (account.CustomProperties?.Any() == true)
        {
            accountDTO.CustomFields = account.CustomProperties
                .ToDictionary(cp => cp.Name, cp => cp.Value);
        }

        // Apply security filtering - don't expose sensitive data
        if (!HasPermissionToViewSensitiveData())
        {
            accountDTO.BankAccountNumber = null;
            accountDTO.CreditLimit = null;
        }

        return accountDTO;
    }

    private static string InferDataType(object value)
    {
        return value switch
        {
            int or long => "Integer",
            decimal or double or float => "Decimal",
            bool => "Boolean",
            DateTime => "DateTime",
            _ => "String"
        };
    }

    private static bool HasPermissionToViewSensitiveData()
    {
        // Implementation would check current user permissions
        return true; // Simplified for example
    }
}
```

### **2. External Data Format Transformation**

```csharp
public class DataTransformationService
{
    public async Task<Result<List<T>>> TransformExternalDataAsync<T>(
        Stream inputStream,
        DataFormat inputFormat,
        IDataTransformationRules<T> transformationRules) where T : class
    {
        try
        {
            var rawData = await ReadRawDataAsync(inputStream, inputFormat);
            var transformedData = new List<T>();

            foreach (var rawItem in rawData)
            {
                var transformResult = await transformationRules.TransformAsync(rawItem);
                if (transformResult.Status == ResultStatus.Success)
                {
                    transformedData.Add(transformResult.Data);
                }
                else
                {
                    LogTransformationError(rawItem, transformResult.Error);
                }
            }

            return Result<List<T>>.AsSuccess(transformedData);
        }
        catch (Exception ex)
        {
            return Result<List<T>>.AsError($"Data transformation failed: {ex.Message}");
        }
    }

    private async Task<List<Dictionary<string, object>>> ReadRawDataAsync(Stream inputStream, DataFormat format)
    {
        return format switch
        {
            DataFormat.Json => await ReadJsonDataAsync(inputStream),
            DataFormat.Xml => await ReadXmlDataAsync(inputStream),
            DataFormat.Csv => await ReadCsvDataAsync(inputStream),
            DataFormat.Edi => await ReadEdiDataAsync(inputStream),
            _ => throw new NotSupportedException($"Data format {format} is not supported")
        };
    }

    private async Task<List<Dictionary<string, object>>> ReadCsvDataAsync(Stream inputStream)
    {
        var data = new List<Dictionary<string, object>>();

        using var reader = new StreamReader(inputStream);
        var headerLine = await reader.ReadLineAsync();
        if (headerLine == null) return data;

        var headers = headerLine.Split(',').Select(h => h.Trim()).ToArray();

        string line;
        while ((line = await reader.ReadLineAsync()) != null)
        {
            var values = line.Split(',');
            var row = new Dictionary<string, object>();

            for (int i = 0; i < headers.Length && i < values.Length; i++)
            {
                row[headers[i]] = values[i].Trim();
            }

            data.Add(row);
        }

        return data;
    }
}

// Transformation rules for specific data types
public class CustomerTransformationRules : IDataTransformationRules<CustomerDTO>
{
    public async Task<Result<CustomerDTO>> TransformAsync(Dictionary<string, object> rawData)
    {
        try
        {
            var customer = new CustomerDTO
            {
                CompanyName = GetStringValue(rawData, "company_name", "CompanyName"),
                CustomerNumber = GetIntValue(rawData, "customer_no", "CustomerNumber"),
                Email = GetStringValue(rawData, "email", "Email"),
                PhoneNumber = GetStringValue(rawData, "phone", "PhoneNumber")
            };

            // Apply transformation rules
            customer.CompanyName = CleanCompanyName(customer.CompanyName);
            customer.Email = NormalizeEmail(customer.Email);
            customer.PhoneNumber = NormalizePhoneNumber(customer.PhoneNumber);

            // Validate transformed data
            var validationResult = await ValidateCustomer(customer);
            if (validationResult.Status != ResultStatus.Success)
                return Result<CustomerDTO>.FromResult(validationResult);

            return Result<CustomerDTO>.AsSuccess(customer);
        }
        catch (Exception ex)
        {
            return Result<CustomerDTO>.AsError($"Customer transformation failed: {ex.Message}");
        }
    }

    private string GetStringValue(Dictionary<string, object> data, params string[] keys)
    {
        foreach (var key in keys)
        {
            if (data.TryGetValue(key, out var value))
                return value?.ToString();
        }
        return null;
    }

    private int GetIntValue(Dictionary<string, object> data, params string[] keys)
    {
        foreach (var key in keys)
        {
            if (data.TryGetValue(key, out var value) && int.TryParse(value?.ToString(), out var intValue))
                return intValue;
        }
        return 0;
    }

    private string CleanCompanyName(string companyName)
    {
        if (string.IsNullOrWhiteSpace(companyName)) return companyName;

        // Remove extra whitespace and normalize
        return Regex.Replace(companyName.Trim(), @"\s+", " ");
    }

    private string NormalizeEmail(string email)
    {
        if (string.IsNullOrWhiteSpace(email)) return email;

        return email.Trim().ToLowerInvariant();
    }

    private string NormalizePhoneNumber(string phoneNumber)
    {
        if (string.IsNullOrWhiteSpace(phoneNumber)) return phoneNumber;

        // Remove all non-digit characters except +
        var cleaned = Regex.Replace(phoneNumber, @"[^\d+]", "");

        // Add default country code if missing
        if (!cleaned.StartsWith("+") && cleaned.Length >= 10)
        {
            cleaned = "+49" + cleaned; // Default to German numbers
        }

        return cleaned;
    }

    private async Task<Result> ValidateCustomer(CustomerDTO customer)
    {
        if (string.IsNullOrWhiteSpace(customer.CompanyName))
            return Result.AsError("Company name is required");

        if (customer.CustomerNumber <= 0)
            return Result.AsError("Valid customer number is required");

        if (!string.IsNullOrEmpty(customer.Email) && !IsValidEmail(customer.Email))
            return Result.AsError("Invalid email format");

        return Result.AsSuccess();
    }

    private bool IsValidEmail(string email)
    {
        try
        {
            var mailAddress = new MailAddress(email);
            return mailAddress.Address == email;
        }
        catch
        {
            return false;
        }
    }
}
```

## Message Queue and Event Processing Patterns

### **1. Event-Driven Integration Pattern**

```csharp
public interface IEventPublisher
{
    Task<Result> PublishAsync<T>(T eventData, string eventType, Dictionary<string, string> metadata = null) where T : class;
}

public interface IEventSubscriber
{
    Task<Result> SubscribeAsync<T>(string eventType, Func<T, Task<Result>> handler) where T : class;
    Task<Result> UnsubscribeAsync(string eventType);
}

public class MessageQueueEventService : IEventPublisher, IEventSubscriber
{
    private readonly IMessageQueue _messageQueue;
    private readonly IEventSerializer _serializer;
    private readonly ILogger<MessageQueueEventService> _logger;
    private readonly Dictionary<string, List<Func<object, Task<Result>>>> _handlers = new();

    public async Task<Result> PublishAsync<T>(T eventData, string eventType, Dictionary<string, string> metadata = null) where T : class
    {
        try
        {
            var message = new EventMessage
            {
                Id = Guid.NewGuid().ToString(),
                EventType = eventType,
                Data = _serializer.Serialize(eventData),
                Metadata = metadata ?? new Dictionary<string, string>(),
                Timestamp = DateTime.UtcNow
            };

            message.Metadata["ContentType"] = typeof(T).FullName;
            message.Metadata["Publisher"] = Environment.MachineName;

            await _messageQueue.PublishAsync(eventType, message);

            _logger.LogInformation("Published event {EventType} with ID {EventId}", eventType, message.Id);

            return Result.AsSuccess();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to publish event {EventType}", eventType);
            return Result.AsError($"Event publishing failed: {ex.Message}");
        }
    }

    public async Task<Result> SubscribeAsync<T>(string eventType, Func<T, Task<Result>> handler) where T : class
    {
        try
        {
            // Wrap the typed handler
            var wrappedHandler = new Func<object, Task<Result>>(async data =>
            {
                if (data is T typedData)
                {
                    return await handler(typedData);
                }
                return Result.AsError($"Invalid data type for event {eventType}");
            });

            if (!_handlers.ContainsKey(eventType))
            {
                _handlers[eventType] = new List<Func<object, Task<Result>>>();

                // Subscribe to the queue
                await _messageQueue.SubscribeAsync(eventType, ProcessEventMessage);
            }

            _handlers[eventType].Add(wrappedHandler);

            _logger.LogInformation("Subscribed to event {EventType}", eventType);

            return Result.AsSuccess();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to subscribe to event {EventType}", eventType);
            return Result.AsError($"Event subscription failed: {ex.Message}");
        }
    }

    private async Task<Result> ProcessEventMessage(EventMessage message)
    {
        try
        {
            if (!_handlers.ContainsKey(message.EventType))
            {
                _logger.LogWarning("No handlers registered for event type {EventType}", message.EventType);
                return Result.AsSuccess(); // Not an error, just no handlers
            }

            // Deserialize the data
            var contentType = message.Metadata.GetValueOrDefault("ContentType");
            var dataType = Type.GetType(contentType);
            var eventData = _serializer.Deserialize(message.Data, dataType);

            // Process with all registered handlers
            var handlers = _handlers[message.EventType];
            var processingTasks = handlers.Select(handler => ProcessWithHandler(handler, eventData, message));

            var results = await Task.WhenAll(processingTasks);

            // Check if any handlers failed
            var failures = results.Where(r => r.Status != ResultStatus.Success).ToList();
            if (failures.Any())
            {
                _logger.LogError("Event processing failed for {EventType}: {Errors}",
                    message.EventType, string.Join("; ", failures.Select(f => f.Error)));
                return Result.AsError($"Event processing failed: {failures.Count} handlers failed");
            }

            _logger.LogInformation("Successfully processed event {EventType} with {HandlerCount} handlers",
                message.EventType, handlers.Count);

            return Result.AsSuccess();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error processing event {EventType}", message.EventType);
            return Result.AsError($"Event processing failed: {ex.Message}");
        }
    }

    private async Task<Result> ProcessWithHandler(Func<object, Task<Result>> handler, object eventData, EventMessage message)
    {
        try
        {
            return await handler(eventData);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Handler failed for event {EventType} (ID: {EventId})",
                message.EventType, message.Id);
            return Result.AsError($"Handler execution failed: {ex.Message}");
        }
    }
}

// Usage example - Account events
public class AccountEventPublisher
{
    private readonly IEventPublisher _eventPublisher;

    public async Task<Result> NotifyAccountCreatedAsync(AccountDTO account)
    {
        var eventData = new AccountCreatedEvent
        {
            AccountId = account.I3D,
            CompanyName = account.CompanyName,
            CustomerNumber = account.CustomerNumber,
            CreatedBy = account.CreatedBy,
            CreatedDate = DateTime.UtcNow
        };

        return await _eventPublisher.PublishAsync(eventData, "AccountCreated", new Dictionary<string, string>
        {
            ["CustomerId"] = account.CustomerNumber.ToString(),
            ["Priority"] = "Normal"
        });
    }

    public async Task<Result> NotifyAccountUpdatedAsync(AccountDTO oldAccount, AccountDTO newAccount)
    {
        var eventData = new AccountUpdatedEvent
        {
            AccountId = newAccount.I3D,
            OldValues = ObjectMapper.Map<Dictionary<string, object>>(oldAccount),
            NewValues = ObjectMapper.Map<Dictionary<string, object>>(newAccount),
            ModifiedBy = newAccount.ModifiedBy,
            ModifiedDate = DateTime.UtcNow
        };

        return await _eventPublisher.PublishAsync(eventData, "AccountUpdated");
    }
}

// Event handlers
public class AccountEventHandler
{
    private readonly IEmailService _emailService;
    private readonly IAuditService _auditService;

    public async Task<Result> HandleAccountCreatedAsync(AccountCreatedEvent eventData)
    {
        try
        {
            // Send welcome email
            await _emailService.SendWelcomeEmailAsync(eventData.AccountId);

            // Log audit entry
            await _auditService.LogBusinessActionAsync(
                "ACCOUNT_CREATED",
                "Account",
                eventData.AccountId,
                new Dictionary<string, object>
                {
                    ["CompanyName"] = eventData.CompanyName,
                    ["CustomerNumber"] = eventData.CustomerNumber
                });

            return Result.AsSuccess();
        }
        catch (Exception ex)
        {
            return Result.AsError($"Failed to handle AccountCreated event: {ex.Message}");
        }
    }
}
```

### **2. File-Based Integration Pattern**

```csharp
public class FileIntegrationService
{
    private readonly IFileProcessor _fileProcessor;
    private readonly IDataTransformationService _transformationService;
    private readonly ILogger<FileIntegrationService> _logger;

    public async Task<Result<FileProcessingResult>> ProcessFileAsync(
        string filePath,
        FileIntegrationConfig config)
    {
        try
        {
            var result = new FileProcessingResult
            {
                FilePath = filePath,
                StartTime = DateTime.UtcNow,
                Config = config
            };

            // Validate file exists and is readable
            if (!File.Exists(filePath))
            {
                return Result<FileProcessingResult>.AsError($"File not found: {filePath}");
            }

            // Create backup if required
            if (config.CreateBackup)
            {
                var backupPath = CreateBackupFile(filePath);
                result.BackupPath = backupPath;
            }

            // Process the file
            using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

            switch (config.ProcessingMode)
            {
                case FileProcessingMode.Import:
                    await ProcessImportFileAsync(fileStream, config, result);
                    break;
                case FileProcessingMode.Export:
                    await ProcessExportFileAsync(fileStream, config, result);
                    break;
                case FileProcessingMode.Transform:
                    await ProcessTransformFileAsync(fileStream, config, result);
                    break;
                default:
                    return Result<FileProcessingResult>.AsError($"Unsupported processing mode: {config.ProcessingMode}");
            }

            // Archive processed file if required
            if (config.ArchiveAfterProcessing)
            {
                var archivePath = ArchiveFile(filePath, config.ArchiveDirectory);
                result.ArchivePath = archivePath;
            }

            result.EndTime = DateTime.UtcNow;
            result.Success = true;

            _logger.LogInformation("File processing completed: {FilePath} - {RecordsProcessed} records in {Duration}ms",
                filePath, result.RecordsProcessed, (result.EndTime - result.StartTime).TotalMilliseconds);

            return Result<FileProcessingResult>.AsSuccess(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "File processing failed: {FilePath}", filePath);
            return Result<FileProcessingResult>.AsError($"File processing failed: {ex.Message}");
        }
    }

    private async Task ProcessImportFileAsync(
        Stream fileStream,
        FileIntegrationConfig config,
        FileProcessingResult result)
    {
        var rawData = await _fileProcessor.ReadDataAsync(fileStream, config.FileFormat);

        foreach (var rawRecord in rawData)
        {
            try
            {
                var transformedRecord = await _transformationService
                    .TransformAsync(rawRecord, config.TransformationRules);

                if (transformedRecord.Status == ResultStatus.Success)
                {
                    var saveResult = await SaveRecordAsync(transformedRecord.Data, config);
                    if (saveResult.Status == ResultStatus.Success)
                    {
                        result.RecordsProcessed++;
                    }
                    else
                    {
                        result.Errors.Add($"Failed to save record: {saveResult.Error}");
                        result.RecordsFailed++;
                    }
                }
                else
                {
                    result.Errors.Add($"Failed to transform record: {transformedRecord.Error}");
                    result.RecordsFailed++;
                }
            }
            catch (Exception ex)
            {
                result.Errors.Add($"Error processing record: {ex.Message}");
                result.RecordsFailed++;
            }
        }
    }

    private string CreateBackupFile(string originalPath)
    {
        var directory = Path.GetDirectoryName(originalPath);
        var fileName = Path.GetFileNameWithoutExtension(originalPath);
        var extension = Path.GetExtension(originalPath);
        var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");

        var backupPath = Path.Combine(directory, $"{fileName}_backup_{timestamp}{extension}");
        File.Copy(originalPath, backupPath);

        return backupPath;
    }

    private string ArchiveFile(string filePath, string archiveDirectory)
    {
        if (!Directory.Exists(archiveDirectory))
        {
            Directory.CreateDirectory(archiveDirectory);
        }

        var fileName = Path.GetFileName(filePath);
        var archivePath = Path.Combine(archiveDirectory, fileName);

        // Add timestamp if file already exists
        if (File.Exists(archivePath))
        {
            var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
            var extension = Path.GetExtension(fileName);
            var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
            archivePath = Path.Combine(archiveDirectory, $"{nameWithoutExt}_{timestamp}{extension}");
        }

        File.Move(filePath, archivePath);
        return archivePath;
    }
}
```

## Integration Best Practices and Guidelines

### **1. Error Handling and Recovery**
- **Graceful Degradation**: System continues to function when external services are unavailable
- **Retry Logic**: Implement exponential backoff for transient failures
- **Circuit Breakers**: Prevent cascade failures in distributed systems
- **Dead Letter Queues**: Handle messages that cannot be processed

### **2. Data Consistency and Synchronization**
- **Idempotent Operations**: Operations can be safely repeated without side effects
- **Event Sourcing**: Track all changes as events for audit and replay capability
- **Eventual Consistency**: Accept temporary inconsistency for better performance
- **Compensation Patterns**: Implement compensating actions for failed operations

### **3. Security and Authentication**
- **API Keys and Tokens**: Secure external API authentication
- **Certificate Validation**: Verify SSL/TLS certificates in production
- **Data Encryption**: Encrypt sensitive data in transit and at rest
- **Input Validation**: Validate all external data before processing

### **4. Monitoring and Observability**
- **Integration Health Checks**: Monitor external service availability
- **Performance Metrics**: Track response times and throughput
- **Error Tracking**: Log and monitor integration failures
- **Distributed Tracing**: Track requests across service boundaries

## Conclusion

The Centron application implements comprehensive integration patterns that provide:

- **Resilience**: Circuit breakers and retry policies ensure system stability
- **Flexibility**: Multiple integration channels support various external systems
- **Reliability**: Error handling and recovery mechanisms prevent data loss
- **Scalability**: Asynchronous processing and message queues handle high volumes
- **Maintainability**: Clear patterns and abstractions simplify integration management
- **Security**: Authentication and validation protect against external threats
- **Observability**: Comprehensive monitoring provides visibility into integration health

These integration patterns enable seamless communication between the Centron application and external systems while maintaining reliability, security, and performance under various conditions.