<?php

namespace Dealerdirect\Agreements\Models;

use Dealerdirect\Agreements\DataObjects;
use Dealerdirect\Agreements\Enums\Locale;
use Dealerdirect\Agreements\Exceptions\DeliveryNotFoundException;
use Dealerdirect\Agreements\Utils\CommentsFormatter;
use Dealerdirect\Agreements\Utils\GeneralUtils;
use DealerDirect\Generic\Category\DamageFree;
use DealerDirect\Generic\Category\Driveable;
use DealerDirect\Generic\Category\VehicleTypeCarCondition;
use DealerDirect\Generic\Category\VehicleTypeMotorCondition;
use DealerDirect\Generic\Repository\CallGroupInformation;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;

abstract class AbstractModel
{
    protected ?string $locale = null;

    public function __construct(
        public readonly Connection $connection,
        public readonly int $deliverId
    ) {
    }

    public function getSaleData(): DataObjects\SalesAgreement
    {
        $data = $this->fetchData($this->getSaleQuery(), ['deliverId' => $this->deliverId]);

        $comments = (new CommentsFormatter($data['internal_comments'], $data['consumer_comments']))->format();
        $data['internal_comments'] = $comments['internal_comments'];
        $data['consumer_comments'] = $comments['consumer_comments'];
        $data['drivable'] ??= $comments['drivable'];
        $data['vin'] ??= $comments['vin'];

        $locale = Locale::fromSiteReference((int) $data['site_reference']);
        $vehicleType = GeneralUtils::getVehicleTypeBySiteReference((int) $data['site_reference']);

        return new DataObjects\SalesAgreement(
            locale: $locale,
            vehicleType: $vehicleType,
            lotId: (int) $data['lot_id'],
            deliverId: $this->deliverId,
            siteReference: (int) $data['site_reference'],
            consumer: $this->makeConsumer($data),
            vehicle: $this->makeVehicle($data, $vehicleType),
            company: $this->makeCompany($data),
            salesAgent: new DataObjects\Agent($data['sales_agent'], $data['sales_agent_name']),
            internalComments: $comments['internal_comments'],
            consumerComments: $comments['consumer_comments'],
            pickUp: (bool) $data['pick_up'],
            soldDate: new \DateTime($data['sales_date']),
            salesPrice: (int) $data['sales_price'],
            salesFee: (int) $data['sales_fee'],
        );
    }

    public function getPurchaseData(): DataObjects\PurchaseAgreement
    {
        $data = $this->fetchData($this->getPurchaseQuery(), ['deliverId' => $this->deliverId]);
        $vehicleType = GeneralUtils::getVehicleTypeBySiteReference((int) $data['site_reference']);

        $comments = (new CommentsFormatter($data['internal_comments'], $data['consumer_comments']))->format();
        $data['internal_comments'] = $comments['internal_comments'];
        $data['consumer_comments'] = $comments['consumer_comments'];
        $data['drivable'] ??= $comments['drivable'];
        $data['vin'] ??= $comments['vin'];

        return new DataObjects\PurchaseAgreement(
            deliverId: $this->deliverId,
            lotId: (int) $data['lot_id'],
            siteReference: (int) $data['site_reference'],
            callGroupId: (new CallGroupInformation($this->connection, $vehicleType))->getCallGroupId($data['lot_id']),
            locale: Locale::fromSiteReference((int) $data['site_reference']),
            vehicleType: $vehicleType,
            consumer: $this->makeConsumer($data),
            vehicle: $this->makeVehicle($data, $vehicleType),
            company: $this->makeCompany($data),
            salesAgent: new DataObjects\Agent($data['sales_agent'], $data['sales_agent_name']),
            salesPrice: (int) $data['sales_price'],
            salesFee: (int) $data['sales_fee'],
            pickUp: (int) $data['pick_up'],
            soldDate: new \DateTime($data['sales_date']),
            internalComments: $data['internal_comments'],
            consumerComments: $data['consumer_comments'],
        );
    }

    /**
     * {@inheritDoc}
     */
    public function log(string $action, int $user = 0, ?string $ipAddress = null): void
    {
        $this->connection->insert($this->getLogTable(), [
            'leveringref' => $this->deliverId,
            'actie' => $action,
            'gebruiker' => $user,
            'ip' => $ipAddress ?? ($_SERVER['REMOTE_ADDR'] ?? ''),
        ]);
    }

    /**
     * @param  array<string,mixed>      $params
     * @return array<string,int|string>
     */
    protected function fetchData(string $query, array $params): array
    {
        $data = $this->connection->fetchAssociative($query, $params);
        if (!is_array($data) || empty($data)) {
            throw new DeliveryNotFoundException($query, $params, get_called_class());
        }

        return $data;
    }

    /**
     * @param array<string,int|string> $data
     */
    private function makeConsumer(array $data): DataObjects\Contact
    {
        return new DataObjects\Contact(
            id: null,
            firstName: $data['consumer_first_letters'],
            lastName: $data['consumer_name'],
            salutation: $data['consumer_salutation'] ?? null,
            name: "{$data['consumer_first_letters']} {$data['consumer_name']}",
            streetName: $data['consumer_street_name'],
            houseNumber: $data['consumer_house_number'],
            zipCode: $data['consumer_zip_code'],
            city: $data['consumer_city'],
            country: (int) $data['consumer_country'],
            phoneNumbers: array_unique(array_filter([$data['consumer_phone'], $data['consumer_mobile']])),
            emails: array_filter([$data['consumer_email']]),
        );
    }

    /**
     * @param array<string,int|string> $data
     */
    private function makeVehicle(array $data, string $vehicleType): DataObjects\Vehicle
    {
        return new DataObjects\Vehicle(
            brand: $data['make'],
            model: $data['model'],
            licensePlate: $data['license_plate'],
            bodyType: $data['bodywork'],
            vehicleTrim: $data['execution'],
            engineCapacity: $data['engine_capacity'] ?? null,
            fuel: $data['fuel'] ?? null,
            enginePowers: array_filter([$data['power'] ?? null, $data['power_2'] ?? null]),
            transmission: $data['transmission'] ?? null,
            odometerReading: $data['mileage'] ? (int) $data['mileage'] : null,
            odometerReadingUnit: !empty($data['mileage_type']) ? (int) $data['mileage_type'] : null,
            constructionYear: (int) $data['construction_year'],
            inspectionValidUntil: GeneralUtils::dateTimeOrNull($data['inspection_valid_until'] ?? null),
            firstAscriptionDate: GeneralUtils::dateTimeOrNull($data['first_ascription'] ?? null),
            colors: array_filter([
                $data['color'] ?? null,
                $data['color_2'] ?? null,
                $data['color_3'] ?? null,
            ], 'is_numeric'),
            cylinderAmount: $data['cylinder_amount'] ?? null,
            paintType: is_numeric($data['paint_type'] ?? null) ? (int) $data['paint_type'] : null,
            upholstery: !empty($data['upholstery']) ? (int) $data['upholstery'] : null,
            marginVat: isset($data['marge']) ? (int) $data['marge'] : null,
            options: $this->mapVehicleOptions($data['vehicle_options'] ?? ''),
            registrationPossible: is_numeric($data['registration_possible'] ?? null)
                ? (int) $data['registration_possible'] : null,
            wokNotification: is_numeric($data['wok_notification'] ?? null) ? (int) $data['wok_notification'] : null,
            maintenanceHistory: is_numeric($data['maintenance_history'] ?? null)
                ? (int) $data['maintenance_history']
                : null,
            vin: $data['vin'] ?? null,
            bicycleType: !empty($data['bicycle_type']) ? (int) $data['bicycle_type'] : null,
            enginePosition: !empty($data['engine_position']) ? (int) $data['engine_position'] : null,
            batteryWattage: !empty($data['battery_wattage']) ? (int) $data['battery_wattage'] : null,
            amountOfGears: !empty($data['gears']) ? (int) $data['gears'] : null,
            oldNewModel: $data['old_new_model'] ?? null,
            image: $data['image'] ?? null,
            conditions: $this->makeConditions($data, $vehicleType),
            checklist: $this->getChecklist($data['checklist_id']),
        );
    }

    private function mapVehicleOptions(string $vehicleOptions): array
    {
        $options = [];
        foreach (explode(';', $vehicleOptions) as $option) {
            if (!empty($option)) {
                $options[] = (int) $option;
            }
        }

        return $options;
    }

    /**
     * @param array<string,int|string> $data
     */
    private function makeConditions(array $data, string $vehicleType): DataObjects\Conditions
    {
        $defaultCondition = GeneralUtils::isCar($vehicleType)
            ? VehicleTypeCarCondition::UNKNOWN
            : VehicleTypeMotorCondition::UNKNOWN;

        return new DataObjects\Conditions(
            exterior: $data['condition_exterior'] ?? $defaultCondition,
            technical: $data['condition_technical'] ?? $defaultCondition,
            drivable: $data['drivable'] ?? Driveable::UNKNOWN,
            damageFree: $data['damage_free'] ?? DamageFree::UNKNOWN,
            interior: $data['condition_interior'] ?? $defaultCondition,
            tireProfile: $data['condition_tire_profile'] ?? $defaultCondition,
            frontTireProfile: $data['condition_front_tire'] ?? $defaultCondition,
            rearTireProfile: $data['condition_rear_tire'] ?? $defaultCondition,
            propulsion: $data['condition_propulsion'] ?? $defaultCondition,
            general: $data['condition_general'] ?? $defaultCondition,
            battery: $data['condition_battery'] ?? $defaultCondition,
        );
    }

    /**
     * @param array<string,int|string> $data
     */
    private function makeCompany(array $data): DataObjects\Contact
    {
        return new DataObjects\Contact(
            id: (int) $data['company_id'],
            name: $data['company_name'],
            firstName: null,
            lastName: null,
            salutation: null,
            streetName: $data['company_street_name'],
            houseNumber: $data['company_house_number'],
            zipCode: $data['company_zip_code'],
            country: $data['company_country'],
            city: $data['company_city'],
            phoneNumbers: [$data['company_phone']],
            emails: array_unique([$data['company_agree_email'], $data['company_agree_email_2']]),
        );
    }

    /**
     * @return null|array{questions:array,answers:array}
     */
    private function getChecklist(?int $checklistId): ?array
    {
        if (empty($checklistId)) {
            return null;
        }

        $results = $this->connection
            ->createQueryBuilder()
            ->select('*')
            ->from('dealer01_ddmain.survey_results')
            ->where('id = :id')
            ->setParameter('id', $checklistId, ParameterType::INTEGER)
            ->fetchAssociative()
        ;

        if (empty($results)) {
            return null;
        }

        $questions = $this->connection
            ->createQueryBuilder()
            ->select('*')
            ->from('dealer01_ddmain.survey_questions')
            ->where('survey_id = :survey_id')
            ->setParameter('survey_id', $results['survey_id'], ParameterType::INTEGER)
            ->fetchAllAssociative()
        ;

        return [
            'questions' => $questions,
            'answers' => json_decode($results['result'], true, flags: JSON_THROW_ON_ERROR),
        ];
    }

    abstract protected function getPurchaseQuery(): string;

    abstract protected function getSaleQuery(): string;

    abstract protected function getLogTable(): string;
}
