<?php

declare(strict_types=1);

namespace backend\models;

use Yii;
use common\models\User;
use yii\helpers\Html;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use backend\traits\JsonExtendedFieldTrait;
use backend\traits\AddressDataTrait;
use backend\traits\StatusManagementTrait;
use backend\traits\BooleanFieldNormalizationTrait;

/**
 * This is the model class for table "accounts".
 *
 * @property string|null $id Primary identifier
 * @property string|null $account_code Account code
 * @property string|null $account_group Account group
 * @property string|null $name Account name
 * @property int|null $stop Stop flag (1=stopped, 0=active)
 * @property int|null $price_number Price number
 * @property string|null $email Account email
 * @property int|null $web_stop Web account stop flag
 * @property string|null $web_email Web email
 * @property float|null $minfreedelivery Minimum free delivery amount
 * @property float|null $deliverysupplement Delivery supplement amount
 * @property string|null $salesperson Salesperson identifier
 * @property int|null $intid ERP integration ID
 * @property string|null $type Account type
 * @property string|null $parentaccount Parent account reference
 * @property string|null $status Account status
 * @property string|null $extended JSON extended data
 * 
 * @property-read Accounts_extended $extendedData Extended account data object
 * @property-read Addressbe|null $addressl Primary address
 * @property-read Addressbe|null $billingAddress Billing address
 * @property-read Addressbe[] $alladdresses All account addresses
 * @property-read Settings[]|null $branches Branch settings
 * @property-read Settings|null $defaultbranch Default branch
 * @property-read Lookuptablebe|null $defaultbranchid Default branch lookup
 * @property-read User[] $user Associated users
 * @property-read string|null $accountcode Account code (with branch override)
 * 
 * @author Andrew Pickstock
 * @since 1.0
 */
class Accounts extends \common\models\Base
{
    use JsonExtendedFieldTrait;
    use AddressDataTrait;
    use StatusManagementTrait;
    use BooleanFieldNormalizationTrait;
    
    /**
     * @var array Child account identifiers
     */
    public array $childAccounts = [];
    
    /**
     * @var array Secure field names that require special handling
     */
    public array $securefields = [];
    
    /**
     * @var array Date field names
     */
    public array $dates = [];
    
    /**
     * @var array Unique identifier field names
     */
    public array $uniqueidentifiers = [];

    /**
     * {@inheritdoc}
     */
    public static function tableName(): string
    {
        return 'accounts';
    }

    /**
     * {@inheritdoc}
     */
    public function rules(): array
    {
        $localrules = [
            [['account_code', 'account_group', 'name', 'type', 'parentaccount'], 'string'],
            [['address1', 'address2', 'address3', 'town', 'county', 'country', 'postcode', 'telephone', 'fax'], 'string'],
            [['minfreedelivery', 'deliverysupplement'], 'number'],
            [['email', 'web_email'], 'string'],
            [['price_number', 'intid'], 'integer'],
            [['stop', 'web_stop'], 'boolean'],
            [['id', 'salesperson'], 'safe'],
            ['name', 'required'],
            ['extended', 'safe'],
            ['status', 'default', 'value' => 'Active'],
            ['stop', 'default', 'value' => 0],
            ['price_number', 'default', 'value' => null],
        ];
        return array_merge(parent::rules(), $localrules);
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels(): array
    {
        $localLabels = [
            'id' => 'ID',
            'account_code' => 'Account',
            'accountcode' => 'Account',
            'account_group' => 'Account Group',
            'name' => 'Name',
            'stop' => 'Stop',
            'price_number' => 'Price No',
            'email' => 'Email  Acc',
            'web_stop' => 'Web  Acc  Stop',
            'web_email' => 'Web  Email',
            'minfreedelivery' => 'Min Delivery',
            'deliverysupplement' => 'Delivery Supplement',
            'intid' => 'ERP ID',
            'salesperson' => 'Sales Person',
            'extended' => 'Extended',
        ];
        return array_merge(parent::attributeLabels(), $localLabels);
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels(): array
    {
        $localLabels = [
            'id' => 'ID',
            'account_code' => 'Account',
            'accountcode' => 'Account',
            'account_group' => 'Account Group',
            'name' => 'Name',
            'stop' => 'Stop',
            'price_number' => 'Price No',
            'email' => 'Email  Acc',
            'web_stop' => 'Web  Acc  Stop',
            'web_email' => 'Web  Email',
            'minfreedelivery' => 'Min Delivery',
            'delvierysupplement' => 'Delivery Supplement',
            'intid' => 'ERP ID',
            'salesperson' => 'Sales Person',
            'extended' => 'Extended',
        ];
        
        // Merge address labels from trait
        $addressLabels = $this->getAddressLabels();
        
        return array_merge(parent::attributeLabels(), $localLabels, $address
        return is_array($this->branches) && $this->defaultbranchid 
            ? $this->defaultbranchid->data 
            : $this->account_code;
    }

    /**
     * Gets all branch settings associated with this account.
     * 
     * @return \yii\db\ActiveQuery
     */
    public function getBranches(): \yii\db\ActiveQuery
    {
        return $this->hasMany(Settings::class, ['id' => 'relatedid'])
            ->via('lookup');
    }
    
    /**
     * Gets the default branch ID lookup record.
     * 
     * @return Lookuptablebe|null
     */
    public function getDefaultbranchid(): ?Lookuptablebe
    {
        return Lookuptablebe::findOne([
            'sourceid' => $this->id,
            'source' => 'Accounts',
            'related' => 'Settings',
            'primaryrecord' => 1,
        ]);
    }
    
    /**
     * Gets the default branch settings.
     * 
     * @return Settings|null
     */
    public function getDefaultbranch(): ?Settings
    {
        return $this->defaultbranchid 
            ? Settings::findOne(['id' => $this->defaultbranchid->relatedid]) 
            : null;
    }
    
    /**
     * Gets all users associated with this account.
     * 
     * @return \yii\db\ActiveQuery
     */
    public function getUser(): \yii\db\ActiveQuery
    {
        return $this->hasMany(User::class, ['account_code' => 'account_code']);
    }
    
    /**
     * Gets the primary address for this account.
     * 
     * @return \yii\db\ActiveQuery
     */
    public function getAddressl(): \yii\db\ActiveQuery
    {
        return $this->hasOne(Addressbe::class, ['accountid' => 'id']);
    }
    
    /**
     * Gets active accounts as an ID-to-name map.
     * 
     * @return array<string, string>
     */
    public static function getActive(): array
    {
        return ArrayHelper::map(
            self::find()
                ->where(['status' => 'Active'])
                ->orderBy(['name' => SORT_ASC])
                ->all(),
            'id',
            'name'
        );
    }
    
    /**
     * Gets active accounts as a code-to-description map with additional details.
     * 
     * @return array<string, string>
     */
    public static function getActiveCode(): array
    {
        $output = [];
        $accounts = self::find()
            ->where(['status' => 'Active'])
            ->orderBy(['name' => SORT_ASC])
            ->all();
            
        foreach ($accounts as $model) {
            $description = $model->account_code . ' || ' . $model->name;
            
            if ($model->stop == 1) {
                $description .= ' (on Stop)';
            }
            
            if ($model->addressl) {
                $description .= ' ' . $model->addressl->town;
            }
            
            $output[$model->account_code] = $description;
        }
        
        return $output;
    }

    /**
     * Gets active accounts of a specific type as intid-to-name map.
     * 
     * @param string $type The account type to filter by
     * @return array<int, string>
     */
    public static function intids(string $type): array
    {
        return ArrayHelper::map(
            self::find()
                ->select(['id', 'status', 'name', 'intid', 'type'])
                ->where(['status' => 'Active', 'type' => $type])
                ->orderBy(['name' => SORT_ASC])
                ->all(),
            'intid',
            'name'
        );
    }

    /**
     * Links a user to this account via lookup table.
     * 
     * @param string $id The user ID to link
     * @return bool Whether the link was created successfully
     */
    public function linkuser(string $id): bool
    {
        $lt = new Lookuptablebe([
            'id' => self::Tempid(), 
            'language' => 'en'
        ]);
        $lt->source = 'User';
        $lt->sourceid = $id;
        $lt->related = 'Account';
        $lt->relatedid = $this->id;
        
        return $lt->save();
    }

    /**
     * Gets billing addresses for the current user's account.
     * 
     * @return array<string, string>
     */
    public static function billingaddresses(): array
    {
        return ArrayHelper::map(
            Addressbe::find()
                ->where(['accountid' => Yii::$app->user->identity->account->id])
                ->orWhere(['relatedid' => Yii::$app->user->identity->id])
                ->andWhere(['or', ['like', 'type', 'Billing'], ['like', 'type', 'Primary']])
                ->andWhere(['not', ['is', 'postcode', null]])
                ->all(),
            'id',
            'address1'
        );
    }

    /**
     * Gets the billing or primary address for this account.
     * 
     * @return \yii\db\ActiveQuery
     */
    public function getBillingAddress(): \yii\db\ActiveQuery
    {
        return $this->hasOne(Addressbe::class, ['accountid' => 'id'])
            ->onCondition(['or like', 'type', ['Primary', 'Billing']]);
    }
    
    /**
     * Gets all active addresses for this account.
     * 
     * @return \yii\db\ActiveQuery
     */
    public function getAlladdresses(): \yii\db\ActiveQuery
    {
        return $this->hasMany(Addressbe::class, ['accountid' => 'id'])
            ->onCondition(['status' => [null, '', 'Active']]);
    }

    /**
     * Gets shipping addresses available to the current user.
     * 
     * @param string|null $accountid Optional account ID to filter by
     * @return array<string, string> Array of address ID to formatted address string
     */
    public static function shippingaddresses(?string $accountid = null): array
    {
        $ids = [];
        
        if (!empty(Yii::$app->user->identity->accountData['accCode'])) {
            $accounts = Accounts::find()->select(['id', 'name']);
            
            if (Yii::$app->user->identity->inGroup('Client Admin') 
                || Yii::$app->user->identity->inGroup('Global Admins')
            ) {
                $parent_account = new \yii\db\Expression("json_value(`accounts`.`extended`, '$.parent_account')");
                $accounts->where([
                    'or', 
                    ['=', $parent_account, Yii::$app->user->identity->accountData['accCode']], 
                    ['account_code' => Yii::$app->user->identity->accountData['accCode']]
                ]);
            } else {
                $accounts->where(['account_code' => Yii::$app->user->identity->accountData['accCode']]);
            }

            $ids = array_column(
                $accounts->andWhere(['status' => 'Active'])
                    ->orderBy(['account_code' => SORT_ASC])
                    ->asArray()
                    ->all(), 
                'id'
            );
        }

        $query = Addressbe::find()
            ->where(['not', ['in', 'postcode', ['', null, ' ']]])
            ->andWhere(['not', ['is', 'postcode', null]]);
            
        if ($accountid !== null && !Yii::$app->user->isGuest) {
            $query->andFilterWhere(['in', 'accountid', $ids]);
        } elseif ($accountid === null && !Yii::$app->user->isGuest) {
            $query->andWhere(['relatedid' => Yii::$app->user->identity->id]);
        }
        
        $query->orderBy(['companyname' => SORT_ASC]);
        
        $output = [];
        foreach ($query->all() as $model) {
            $output[$model->id] = Html::decode(
                str_replace('<br />', ', ', $model->fulladdress)
            );
        }
        
        return $output;
    }

    /**
     * {@inheritdoc}
     */
    public function beforeValidate(): bool
    {
        // Normalize boolean fields using trait
        $this->normalizeBooleanFields(['stop', 'web_stop']);
        
        // Ensure account_group is string
        $this->account_group = strval($this->account_group);
        
        // Normalize price_number
        $this->price_number = $this->price_number === 'M' ? 1 : $this->price_number;
        
        return parent::beforeValidate();
    }
    
    /**
     * {@inheritdoc}
     * 
     * Note: For new code, consider using AccountService::saveWithAddress() instead
     * for better transaction handling and separation of concerns.
     */
    public function beforeSave($insert): bool
    {
        // Encode extended data using trait
        $this->encodeExtendedField();
        
        // Encode data field for CRM if applicable
        if (Yii::$app->params['crm'] ?? false) {
            if (is_object($this->data) || is_array($this->data)) {
                $this->data = Json::encode($this->data);
            }
        }
        
        // Save associated address data if virtual properties are set
        // This maintains backward compatibility with existing code
        if ($this->hasAddressData()) {
            $this->saveAddressData();
        }
        
        return parent::beforeSave($insert);
    }

    /**
     * Checks if any address data has been set on virtual properties.
     * This method is provided by AddressDataTrait.
     * 
     * @return bool
     */
    // Method provided by AddressDataTrait

    /**
     * Saves or updates the associated address record.
     * 
     * @deprecated Use AccountService::saveWithAddress() for new code
     * @return void
     */
    protected function saveAddressData(): void
    {
        $address = $this->addressl instanceof Addressbe 
            ? $this->addressl 
            : new Addressbe();
        
        // Get address data from trait
        $addressData = $this->getAddressData();
        
        // Only update fields if they have non-empty values
        foreach ($addressData as $field => $value) {
            if ($value !== null && trim((string)$value) !== '') {
                $address->$field = $value;
            }
        }
        
        // Set company name from account name
        if ($this->name && trim($this->name)) {
            $address->companyname = $this->name;
        }
        
        if ($this->id && trim($this->id)) {
            $address->accountid = $this->id;
        }
        
        $address->type = 'Primary';
        
        if (!$address->save()) {
            Yii::error('Failed to save address: ' . Json::encode($address->errors), __METHOD__);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function afterSave($insert, $changedAttributes): void
    {
        parent::afterSave($insert, $changedAttributes);
    }

    /**
     * {@inheritdoc}
     */
    public function afterFind(): void
    {
        // Load extended data using trait
        $this->extended = $this->getExtendedData();
        
        // Load CRM data if applicable
        if ((Yii::$app->params['crm'] ?? false) && array_key_exists('data', $this->attributes)) {
            $this->data = \backend\models\AccountsData::Data($this->data);
        }
        
        parent::afterFind();
    }
    
    /**
     * Determines the extended data class to use.
     * Required by JsonExtendedFieldTrait.
     * 
     * @return string The fully qualified class name
     */
    protected function getExtendedClass(): string
    {
        $aliases = \backend\models\System::getAliases();
        
        if (isset($aliases['frontend']) 
            && file_exists(Yii::getAlias('@frontend') . '/models/Accounts_extended.php')
        ) {
            return \frontend\models\Accounts_extended::class;
        }
        
        return \backend\models\Accounts_extended::class;
    }
    
    /**
     * {@inheritdoc}
     */
    public function init(): void
    {
        if (empty($this->extended)) {
            $this->extended = $this->getExtendedData();
        }
        
        parent::init();
    }

    /**
     * Gets address details for a given account code.
     * 
     * @param string $account_code The account code
     * @return array|null Address details or null if not found
     */
    public function getAddressDetails(string $account_code): ?array
    {
        $account = self::find()
            ->where(['account_code' => $account_code])
            ->one();
            
        if (!$account || !$account->addressl) {
            return null;
        }
        
        return \backend\models\Address::loadAddress($account->addressl->id);
    }
}
