<?php

declare(strict_types=1);

namespace backend\services;

use Yii;
use backend\models\Accounts;
use backend\models\Addressbe;
use backend\models\Lookuptablebe;
use yii\helpers\Json;
use yii\base\InvalidArgumentException;

/**
 * AccountService handles business logic for account operations.
 * 
 * This service layer separates business logic from the model layer,
 * making the code more testable and maintainable while preparing
 * for Yii3 migration.
 * 
 * Example usage:
 * ```php
 * $service = new AccountService();
 * $account = new Accounts(['name' => 'Example Corp']);
 * 
 * if ($service->saveWithAddress($account, [
 *     'address1' => '123 Main St',
 *     'town' => 'London',
 *     'postcode' => 'SW1A 1AA',
 * ])) {
 *     // Success
 * }
 * ```
 * 
 * @author Andrew Pickstock
 * @since 1.0
 */
class AccountService
{
    /**
     * Saves an account with its associated address in a transaction.
     * 
     * @param Accounts $account The account model to save
     * @param array $addressData Address data to save (optional)
     * @return bool Whether the save was successful
     * @throws \Throwable If the transaction fails
     */
    public function saveWithAddress(Accounts $account, array $addressData = []): bool
    {
        $transaction = Yii::$app->db->beginTransaction();
        
        try {
            // Prepare account data before saving
            $this->prepareAccountForSave($account);
            
            // Save the account
            if (!$account->save()) {
                throw new InvalidArgumentException('Failed to save account: ' . Json::encode($account->errors));
            }
            
            // Save associated address if data provided
            if (!empty($addressData)) {
                $this->saveAccountAddress($account, $addressData);
            }
            
            $transaction->commit();
            return true;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            Yii::error('Failed to save account with address: ' . $e->getMessage(), __METHOD__);
            throw $e;
        }
    }
    
    /**
     * Prepares account data before saving (normalization, encoding, etc).
     * 
     * @param Accounts $account The account to prepare
     * @return void
     */
    protected function prepareAccountForSave(Accounts $account): void
    {
        // Encode extended data if it's an object or array
        if (is_object($account->extended) || is_array($account->extended)) {
            $account->extended = Json::encode($account->extended, JSON_UNESCAPED_UNICODE);
        }
        
        // Encode data field for CRM if applicable
        if ($this->isCrmEnabled() && isset($account->data)) {
            if (is_object($account->data) || is_array($account->data)) {
                $account->data = Json::encode($account->data);
            }
        }
    }
    
    /**
     * Saves or updates the address associated with an account.
     * 
     * @param Accounts $account The account to save address for
     * @param array $addressData Address data to save
     * @return bool Whether the save was successful
     */
    protected function saveAccountAddress(Accounts $account, array $addressData): bool
    {
        // Load existing address or create new one
        $address = $this->getOrCreateAddress($account);
        
        // Update address fields from provided data
        $this->updateAddressFields($address, $account, $addressData);
        
        // Save the address
        if (!$address->save()) {
            Yii::error('Failed to save address: ' . Json::encode($address->errors), __METHOD__);
            throw new InvalidArgumentException('Failed to save address');
        }
        
        return true;
    }
    
    /**
     * Gets the existing address for an account or creates a new one.
     * 
     * @param Accounts $account The account
     * @return Addressbe The address model
     */
    protected function getOrCreateAddress(Accounts $account): Addressbe
    {
        // Try to get existing address
        $address = $account->addressl;
        
        // If no address exists, create new one
        if (!$address instanceof Addressbe) {
            $address = new Addressbe();
            $address->accountid = $account->id;
            $address->type = 'Primary';
        }
        
        return $address;
    }
    
    /**
     * Updates address fields from account and provided address data.
     * 
     * @param Addressbe $address The address model to update
     * @param Accounts $account The account model
     * @param array $addressData Address data to apply
     * @return void
     */
    protected function updateAddressFields(Addressbe $address, Accounts $account, array $addressData): void
    {
        // Map of address data fields to address model properties
        $fieldMap = [
            'companyname' => $account->name ?? $addressData['companyname'] ?? null,
            'address1' => $addressData['address1'] ?? $account->address1 ?? null,
            'address2' => $addressData['address2'] ?? $account->address2 ?? null,
            'address3' => $addressData['address3'] ?? $account->address3 ?? null,
            'town' => $addressData['town'] ?? $account->town ?? null,
            'county' => $addressData['county'] ?? $account->county ?? null,
            'country' => $addressData['country'] ?? $account->country ?? null,
            'postcode' => $addressData['postcode'] ?? $account->postcode ?? null,
            'telephone' => $addressData['telephone'] ?? $account->telephone ?? null,
            'contact' => $addressData['contact'] ?? $account->contact ?? null,
            'fax' => $addressData['fax'] ?? $account->fax ?? null,
        ];
        
        // Update only non-empty fields
        foreach ($fieldMap as $field => $value) {
            if ($value !== null && trim((string)$value) !== '') {
                $address->$field = $value;
            }
        }
        
        // Ensure required fields are set
        $address->accountid = $account->id;
        $address->type = $addressData['type'] ?? 'Primary';
    }
    
    /**
     * Links a user to an account via lookup table.
     * 
     * @param Accounts $account The account to link to
     * @param string $userId The user ID to link
     * @return bool Whether the link was created successfully
     */
    public function linkUserToAccount(Accounts $account, string $userId): bool
    {
        $lookup = new Lookuptablebe([
            'id' => Accounts::Tempid(),
            'language' => 'en',
            'source' => 'User',
            'sourceid' => $userId,
            'related' => 'Account',
            'relatedid' => $account->id,
        ]);
        
        if (!$lookup->save()) {
            Yii::error('Failed to link user to account: ' . Json::encode($lookup->errors), __METHOD__);
            return false;
        }
        
        return true;
    }
    
    /**
     * Creates a new account with address in one operation.
     * 
     * @param array $accountData Account attributes
     * @param array $addressData Address attributes
     * @return Accounts|null The created account or null on failure
     */
    public function createAccountWithAddress(array $accountData, array $addressData = []): ?Accounts
    {
        $account = new Accounts();
        $account->setAttributes($accountData);
        
        if ($this->saveWithAddress($account, $addressData)) {
            return $account;
        }
        
        return null;
    }
    
    /**
     * Updates an account and its address.
     * 
     * @param Accounts $account The account to update
     * @param array $accountData New account data
     * @param array $addressData New address data
     * @return bool Whether the update was successful
     */
    public function updateAccountWithAddress(
        Accounts $account, 
        array $accountData = [], 
        array $addressData = []
    ): bool {
        if (!empty($accountData)) {
            $account->setAttributes($accountData);
        }
        
        return $this->saveWithAddress($account, $addressData);
    }
    
    /**
     * Deletes an account and its related data.
     * 
     * @param Accounts $account The account to delete
     * @param bool $deleteAddresses Whether to delete associated addresses
     * @return bool Whether the deletion was successful
     */
    public function deleteAccount(Accounts $account, bool $deleteAddresses = true): bool
    {
        $transaction = Yii::$app->db->beginTransaction();
        
        try {
            // Delete addresses if requested
            if ($deleteAddresses) {
                foreach ($account->alladdresses as $address) {
                    $address->delete();
                }
            }
            
            // Soft delete by setting status
            $account->status = 'Deleted';
            
            if (!$account->save(false)) {
                throw new InvalidArgumentException('Failed to delete account');
            }
            
            $transaction->commit();
            return true;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            Yii::error('Failed to delete account: ' . $e->getMessage(), __METHOD__);
            return false;
        }
    }
    
    /**
     * Validates if an account code is available (not already in use).
     * 
     * @param string $accountCode The account code to check
     * @param string|null $excludeId Account ID to exclude from check (for updates)
     * @return bool Whether the account code is available
     */
    public function isAccountCodeAvailable(string $accountCode, ?string $excludeId = null): bool
    {
        $query = Accounts::find()
            ->where(['account_code' => $accountCode])
            ->andWhere(['!=', 'status', 'Deleted']);
        
        if ($excludeId !== null) {
            $query->andWhere(['!=', 'id', $excludeId]);
        }
        
        return $query->count() === 0;
    }
    
    /**
     * Sets the stop flag for an account.
     * 
     * @param Accounts $account The account to update
     * @param bool $stopped Whether to stop the account
     * @return bool Whether the update was successful
     */
    public function setAccountStop(Accounts $account, bool $stopped): bool
    {
        $account->stop = $stopped ? 1 : 0;
        return $account->save(false, ['stop']);
    }
    
    /**
     * Checks if CRM functionality is enabled.
     * 
     * @return bool
     */
    protected function isCrmEnabled(): bool
    {
        return Yii::$app->params['crm'] ?? false;
    }
    
    /**
     * Gets account statistics.
     * 
     * @return array Statistics about accounts
     */
    public function getAccountStats(): array
    {
        return [
            'total' => Accounts::find()->count(),
            'active' => Accounts::find()->where(['status' => 'Active'])->count(),
            'stopped' => Accounts::find()->where(['stop' => 1])->count(),
            'web_stopped' => Accounts::find()->where(['web_stop' => 1])->count(),
        ];
    }
    
    /**
     * Gets linked accounts based on account code pattern.
     * 
     * @param Accounts $model The main account model
     * @return array List of linked accounts
     */
    public function getLinkedAccounts(Accounts $model): array
    {
        if (empty($model->account_code)) {
            return [];
        }
        
        return Accounts::find()
            ->where(['!=', 'account_code', $model->account_code])
            ->andWhere(['LIKE', 'account_code', $model->account_code])
            ->all();
    }
    
    /**
     * Searches for accounts by code or name.
     * 
     * @param string $query The search query
     * @param string|null $type Optional account type filter (e.g., 'Agent')
     * @return array List of matching accounts
     */
    public function searchAccounts(string $query, ?string $type = null): array
    {
        $queryBuilder = Accounts::find()
            ->where(['or',
                ['like', 'account_code', $query],
                ['like', 'name', $query]
            ]);
        
        if ($type !== null) {
            $queryBuilder->andWhere(['type' => $type]);
        }
        
        return $queryBuilder
            ->orderBy(['account_code' => SORT_ASC])
            ->all();
    }
    
    /**
     * Formats account models into Select2-compatible format.
     * 
     * @param array $accounts List of account models
     * @return array Formatted results array
     */
    public function formatAccountResults(array $accounts): array
    {
        $output = ['results' => []];
        
        foreach ($accounts as $model) {
            $text = $this->buildAccountDisplayText($model);
            
            $output['results'][] = [
                'id' => $model->account_code,
                'text' => $text,
            ];
        }
        
        return $output;
    }
    
    /**
     * Builds display text for an account in search results.
     * 
     * @param Accounts $model The account model
     * @return string Formatted display text
     */
    public function buildAccountDisplayText(Accounts $model): string
    {
        $parts = [trim($model->account_code)];
        
        if (!empty($model->name)) {
            $parts[] = $model->name;
        }
        
        if ($model->addressl !== null && !empty($model->addressl->town)) {
            $parts[] = $model->addressl->town;
        }
        
        return implode(' || ', $parts);
    }
}
