<?php

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\RequestException;

class OdooClient
{
    protected Client $http;
    protected CookieJar $jar;
    protected string $url;
    protected string $db;
    protected string $login;
    protected string $password;
    protected ?int $uid = null;

    public function __construct()
    {
        $this->url = rtrim(config('services.odoo.url', env('ODOO_URL')), '/');
        $this->db = config('services.odoo.db', env('ODOO_DB'));
        $this->login = config('services.odoo.login', env('ODOO_LOGIN'));
        $this->password = config('services.odoo.password', env('ODOO_PASSWORD'));

        $this->jar = new CookieJar();
        $this->http = new Client([
            'base_uri' => $this->url,
            'cookies'  => $this->jar,
            'headers'  => [
                'Content-Type' => 'application/json',
                'Accept'       => 'application/json',
            ],
            // 'verify' => false, // <- only if you have self-signed SSL (not recommended in prod)
            'timeout' => 30,
        ]);
    }

    public function authenticate(): int
    {
        if ($this->uid !== null) {
            return $this->uid;
        }

        $payload = [
            'jsonrpc' => '2.0',
            'params'  => [
                'db'       => $this->db,
                'login'    => $this->login,
                'password' => $this->password,
            ],
        ];

        try {
            $res = $this->http->post('/web/session/authenticate', ['json' => $payload]);
            $data = json_decode((string) $res->getBody(), true);

            if (!empty($data['result']['uid'])) {
                $this->uid = (int) $data['result']['uid'];
                return $this->uid;
            }

            throw new \RuntimeException('Invalid Odoo auth response: '.json_encode($data));
        } catch (RequestException $e) {
            throw new \RuntimeException('Odoo auth failed: '.$e->getMessage(), 0, $e);
        }
    }

    /**
     * Generic JSON-RPC call_kw helper (search_read, read, search_count, etc.)
     */
    public function callKw(string $model, string $method, array $args = [], array $kwargs = [])
    {
        $this->authenticate(); // ensures session/cookies are set

        $payload = [
            'jsonrpc' => '2.0',
            'method'  => 'call',
            'params'  => [
                'model'  => $model,
                'method' => $method,
                'args'   => $args,
                'kwargs' => $kwargs,
            ],
        ];

        try {
            $res = $this->http->post('/web/dataset/call_kw/'.$model.'/'.$method, [
                'json' => $payload,
            ]);
            $data = json_decode((string) $res->getBody(), true);

            if (array_key_exists('result', $data)) {
                return $data['result'];
            }

            // Odoo often returns errors as "error" field
            if (!empty($data['error'])) {
                $msg = $data['error']['message'] ?? 'Unknown Odoo error';
                throw new \RuntimeException('Odoo error: '.$msg.' | '.json_encode($data['error']));
            }

            throw new \RuntimeException('Unexpected Odoo response: '.json_encode($data));
        } catch (RequestException $e) {
            throw new \RuntimeException('Odoo request failed: '.$e->getMessage(), 0, $e);
        }
    }

    /**
     * Get products with pagination. Choose template vs variant via $model.
     *
     * @param string $model              'product.template' (default) or 'product.product'
     * @param array  $domain             Odoo domain (filters)
     * @param array  $fields             Fields to return
     * @param int    $limit              Page size
     * @param int    $offset             Offset for pagination
     * @param array  $kwargsExtra        Extra kwargs (order, context, etc.)
     */
    public function getProducts(
        string $model = 'product.template',
        array $domain = [],
        array $fields = ['id','name','list_price','standard_price','type','uom_id','categ_id','default_code','barcode','active'],
        int $limit = 100,
        int $offset = 0,
        array $kwargsExtra = []
    ): array {
        $kwargs = array_merge([
            'fields' => $fields,
            'limit'  => $limit,
            'offset' => $offset,
            // 'order'  => 'id asc',
            // 'context' => ['lang' => 'en_US'],
        ], $kwargsExtra);

        // args: [domain, fields?] — we pass fields in kwargs to be explicit
        return $this->callKw($model, 'search_read', [$domain], $kwargs);
    }

    /**
     * Get ALL products by paging through.
     */
    public function getAllProducts(
        string $model = 'product.template',
        array $domain = [],
        array $fields = ['id','name','list_price','standard_price','type','uom_id','categ_id','default_code','barcode','active'],
        int $chunkSize = 200,
        array $kwargsExtra = []
    ): array {
        $all = [];
        $offset = 0;

        do {
            $batch = $this->getProducts($model, $domain, $fields, $chunkSize, $offset, $kwargsExtra);
            $all = array_merge($all, $batch);
            $offset += $chunkSize;
        } while (count($batch) === $chunkSize);

        return $all;
    }


    /**
     * Generic create helper.
     * Example: $this->create('res.partner', ['name' => 'Test']);
     */
    public function create(string $model, array $values)
    {
        // For /web/dataset/call_kw, create expects args: [ vals ]
        return $this->callKw($model, 'create', [$values]);
    }

    /**
     * Optional: generic write helper.
     */
    public function write(string $model, array $ids, array $values)
    {
        return $this->callKw($model, 'write', [$ids, $values]);
    }

    /**
     * Create POS order (pos.order) with one2many 'lines'
     */
    public function createPosOrder(array $orderValues)
    {
        return $this->create('pos.order', $orderValues);
    }

    /**
     * Helper: get opened POS session for a config (pos.config)
     */
    public function getOpenPosSessionId(int $posConfigId): ?int
    {
        $sessions = $this->callKw('pos.session', 'search_read', [
            [ // domain
                ['state', '=', 'opened'],
                ['config_id', '=', $posConfigId],
            ]
        ], [
            'fields' => ['id'],
            'limit'  => 1,
        ]);

        if (!empty($sessions[0]['id'])) {
            return (int) $sessions[0]['id'];
        }

        return null;
    }

}
