<?php

namespace Paheko\Plugin\Facturation;

use DateTime;
use Paheko\Config;
use Paheko\DB;
use Paheko\DynamicList;
use Paheko\UserException;
use Paheko\Utils;
use Paheko\Services\Services_User;

class Facture
{
	const TYPES_NAMES = [
		DEVIS => 'Devis',
		FACT  => 'Facture',
	];

	private $keys = [
		'type_facture', // 0 : devis, 1 : facture
		'numero',
		'receveur_membre',
		'receveur_id',
		'date_emission',
		'date_echeance',
		'reglee',
		'archivee',
		'moyen_paiement',
		'contenu',
		'total'
	];

	public $types = [
		DEVIS => [
			'id' => DEVIS,
			'accounts' => [],
			'label' => 'Devis',
			'help' => ''],
		FACT => [
			'id' => FACT,
			'accounts' => [],
			'label' => 'Facture',
			'help' => ''],
	];

	public function __construct()
	{

	}

	// Fix : est dépendant de l'ordre des données dans l'array
	// et implique que toutes les données soient présentes (pas possible de faire un update partiel)
	public function _checkFields(&$datas)
	{
		foreach($datas as $k=>$data)
		{
			if (!in_array($k, $this->keys))
			{
				throw new UserException("Clé inattendue : $k.");
			}

			if(!is_array($data) && null !== $data){
				$datas[$k] = trim($data);
			}
			if ($datas[$k] === '' && $k != 'numero')
			{
				throw new UserException("La valeur de $k est vide");
			}

			switch($k)
			{
				case 'type_facture':
				if (!array_key_exists($datas[$k], $this->types)) {
					throw new UserException("$k est de type non-attendue ($data).");
				}
				if ($datas[$k] < 2) {
					$fac = true;
				}
				elseif ($datas[$k] == 2) {
					$fac = false;
				}
				elseif ($datas[$k] == 3) {
					$fac = false;
				}
				break;
				case 'receveur_membre':
				case 'reglee':
				case 'archivee':
				if ($datas[$k] != 1 && $datas[$k] != 0) {
					throw new UserException("$k est de valeur non-attendue ($data).");
				}
				break;
				case 'receveur_id':
				if (!is_numeric($datas[$k]) || $datas[$k] < 0) {
					throw new UserException("L'id du receveur est non-attendu ($data).");
				}
				break;
				case 'date_emission':
				$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d');
				break;
				case 'date_echeance':
				$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d');
				if (DateTime::createFromFormat('!Y-m-d', $datas[$k])->format('U') < DateTime::createFromFormat('!Y-m-d', $datas['date_emission'])->format('U'))
				{
					throw new UserException("La date d'échéance est antérieure à la date d'émission ($data).");
				}
				break;
				case 'moyen_paiement':
				if (!array_key_exists($datas[$k], $this->listMoyensPaiement())) {
					throw new UserException("Le moyen de paiement ne correspond pas à la liste interne ($data).");
				}
				break;
				case 'contenu':
				if ($fac)
				{
					if (!is_array($datas[$k]) || empty($datas[$k])) {
						throw new UserException("Le contenu du document est vide ($data).");
					}
					$total = 0;
					foreach($datas[$k] as $g => $r)
					{
						if (empty($r['designation']) && empty($r['prix']))
						{
							unset($datas[$k][$g]);
							unset($datas[$k]['prix']);
							continue;
						}
						elseif (empty($r['prix']))
						{
							$datas[$k]['prix'] = 0;
						}

						if (!is_int($r['prix']))
						{
							throw new UserException('Un (ou plus) des prix n\'est pas un entier.');
						}

						$total += $r['prix'];
					}

					if($fac && !$total)
					{
						throw new UserException("Toutes les désignations/prix sont vides.");
					}
				}
				$datas[$k] = json_encode($datas[$k]);
				break;
				case 'total':
				if ($fac && !isset($datas['contenu'])) {
					throw new UserException("Pas de contenu fourni pour vérifier le total.");
				}
				if ($fac && $total != $datas[$k])
				{
					throw new UserException("Les totaux sont différents ($total != $datas[$k].");
				}
				break;
			}
		}
	}

	public function add($data, ?string $number_pattern = null)
	{
		$db = DB::getInstance();

		$this->_checkFields($data);
		$generate_number = false;

		if (empty($data['numero']) && $number_pattern) {
			$generate_number = true;
			$data['numero'] = sha1(random_bytes(10));
		}

		if ($db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE', $data['numero']))
		{
			throw new UserException('Le numéro de document doit être unique, or il existe déjà un document avec le numéro ' . $data['numero']);
		}

		$db->insert('plugin_facturation_factures', $data);
		$id = $db->lastInsertRowId();

		if ($generate_number) {
			$numero = $this->getNewNumber($number_pattern, $data['type_facture'], \DateTime::createFromFormat('!Y-m-d', $data['date_emission']), $id);
			$db->update('plugin_facturation_factures', compact('numero'), 'id = ' . (int) $id);
		}

		return $id;
	}

	/**
	 * Renvoie un nouveau numéro de facture selon un motif défini et le type de document
	 */
	public function getNewNumber(string $pattern, int $type, \DateTimeInterface $date, int $id)
	{
		if ($type == DEVIS) {
			$type = 'DEVIS';
			$t = 'D';
		}
		elseif ($type == FACT) {
			$type = 'FACT';
			$t = 'F';
		}
		$year = $date->format('Y');
		$y = $date->format('y');

		// Garantir l'unicité du numéro
        $db = DB::getInstance();
        $sql = sprintf('SELECT numero FROM plugin_facturation_factures');
        $numeros = array_column($db->get($sql), 'numero');

        //sélectionner les numéros qui correspondent au pattern
        $selpattern = preg_replace('/%(\d+)?\{(ynumber|id)\}/', '', $pattern);
		$data = compact('type', 't', 'year', 'y');
        $prefixe = preg_replace_callback('/%(\d+)?\{([a-z]+)\}/', function ($match) use ($data) {
            $v = (string) $data[$match[2]];
            $type = ctype_digit($v) ? 'd' : 's';
            return sprintf('%' . $match[1] . $type, $v);
        }, $selpattern);
        $modele = '/^' . $prefixe . '\d+$/';
        $numeros_filtres = array_filter($numeros, function($elem) use ($modele) {
            return preg_match($modele, $elem);
        }, 0);

        // extraire le numéro d'ordre
        $rangs = array_map(function($elem) use($prefixe) {
            return (int) substr($elem, strlen($prefixe));
        }, array_values($numeros_filtres));
        sort($rangs);
        if (empty($rangs)) {
            $ynumber = 1;
        } else {
            $ynumber = end($rangs) + 1;
        }

        // fabriquer le numéro selon le pattern
		$data = compact('type', 't', 'year', 'y', 'ynumber', 'id');
		return preg_replace_callback('/%(\d+)?\{([a-z]+)\}/', function ($match) use ($data) {
			$v = (string) $data[$match[2]];
			$type = ctype_digit($v) ? 'd' : 's';
			return sprintf('%' . $match[1] . $type, $v);
		}, $pattern);
	}

	public function get($id)
	{
		$db = DB::getInstance();

		$r = $db->first('SELECT * FROM plugin_facturation_factures WHERE id = ? LIMIT 1;', (int)$id);

		if(!$r)
		{
			throw new UserException("Pas de document retournée avec cet id.");
		}

		if ($r->contenu)
		{
			$r->contenu = json_decode($r->contenu, true);
		}

		$r->date_emission = \DateTime::createFromFormat('!Y-m-d', $r->date_emission);
		if ($r->date_echeance)
		{
			$r->date_echeance= \DateTime::createFromFormat('!Y-m-d', $r->date_echeance);
		}

		return $r;
	}

	public function listAll()
	{
		$r = (array)DB::getInstance()->get('SELECT *, strftime(\'%s\', date_emission) AS date_emission,
			strftime(\'%s\', date_echeance) AS date_echeance
			FROM plugin_facturation_factures');

		foreach ($r as $e)
		{
			if($e->contenu)
			{
				$e->contenu = json_decode((string)$e->contenu, true);
			}
		}

		return $r;
	}

	public function list(): DynamicList
	{
		$id_field = \Paheko\Users\DynamicFields::getNameFieldsSQL('u');
		$plugin_name = preg_replace('/^.*\/(\w+)\/$/', '${1}', \Paheko\PLUGIN_ADMIN_URL);
		$plugin = \Paheko\Plugins::get($plugin_name);

		// adresse et ville peuvent être redéfinies dans la configuration du plugin
		$adresse_client = $plugin->getConfig('adresse_client');
		if ($adresse_client == null) { $adresse = 'u.adresse'; } else { $adresse = 'u.' . $adresse_client; }
		$ville_client = $plugin->getConfig('ville_client');
		if ($ville_client == null) { $ville = 'u.ville'; } else { $ville = 'u.' . $ville_client; }

		$columns = [
			// Sélectionner cette colonne, mais ne pas la mettre dans la liste des colonnes
			// (absence de label)
			'id' => [
				'select' => 'f.id',
			],
			'type_facture' => [
			],
			'receveur_membre' => [
			],
			'receveur_id' => [
			],
			// Créer une colonne virtuelle
			'type' => [
				'label' => 'Type',
				'select' => null,
			],
			'numero' => [
				'label' => 'Numéro',
				'select' => 'f.numero',
			],
			'receveur' => [
				'label' => 'Receveur',
				// l'identité du membre peut être redéfinie dans la configuration des membres
				'select' => sprintf('CASE WHEN receveur_membre THEN CASE %s WHEN "" THEN "** ABSENT **" ELSE %s END ELSE c.nom END', $id_field, $id_field),
			],
			'receveur_adresse' => [
				// l'adresse peut être redéfinie dans la configuration du plugin
				'label' => 'Adresse',
				'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.adresse END', $adresse),
			],
			'receveur_ville' => [
				// la ville peut être redéfinie dans la configuration du plugin
				'label' => 'Ville',
				'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.ville END', $ville),
			],
			'date_emission' => [
				'label' => 'Émission',
				'order' => 'date_emission %s, id %1$s',
			],
			'date_echeance' => [
				'label' => 'Échéance',
			],
			'reglee' => [
				'label' => 'Réglée',
			],
			'archivee' => [
				'label' => 'Archivée',
			],
			'moyen_paiement' => [
				'label' => 'Moyen de paiement',
				'select' => 'mp.nom',
			],
			'contenu' => [
				'label' => 'Contenu',
			],
			'total' => [
				'label' => 'Total',
			],
		];

		$tables = 'plugin_facturation_factures AS f
			INNER JOIN plugin_facturation_paiement AS mp ON mp.code = f.moyen_paiement
			LEFT JOIN users AS u ON f.receveur_membre = 1 AND u.id = f.receveur_id
			LEFT JOIN plugin_facturation_clients AS c ON f.receveur_membre = 0 AND c.id = f.receveur_id';

		$list = new DynamicList($columns, $tables);
		$list->orderBy('date_emission', true);
        $list->setCount('COUNT(f.id)');

		$currency = Config::getInstance()->monnaie;

		$list->setModifier(function ($row) use ($currency) {
			// Remplir la colonne virtuelle
			$row->type = self::TYPES_NAMES[$row->type_facture] ?? null;
			$row->reglee = $row->reglee ? 'Réglée' : 'Non';
			$row->archivee = $row->archivee ? 'Archivée' : 'Non';

			// Remplir le contenu
			$content = json_decode((string)$row->contenu);

			$row->contenu = implode("\n", array_map(function ($row) use ($currency) {
				return sprintf('%s : %s %s', $row->designation, Utils::money_format($row->prix), $currency);
			}, (array)$content));
		});

		return $list;
	}

	public function edit($id, $data = [])
	{
		$db = DB::getInstance();

		$this->_checkFields($data);

		if(isset($data['numero']) && $db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE AND id != ?', $data['numero'], (int)$id))
		{
			throw new UserException('Un document avec ce numéro existe déjà, or le numéro doit être unique.');
		}
		return $db->update('plugin_facturation_factures', $data, $db->where('id', (int)$id));
	}

	public function listUserDoc($base, $id)
	{
		$client = new Client;

		if ($base == 0) // Si c'est un client
		{
			if(!$client->get($id))
			{
				throw new UserException("Ce client n'existe pas.");
			}
		}
		else // Si c'est un membre de l'asso
		{
			throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
		}

		$r = (array)DB::getInstance()->get('SELECT *, strftime(\'%s\', date_emission) AS date_emission,
			strftime(\'%s\', date_echeance) AS date_echeance
			FROM plugin_facturation_factures
			WHERE receveur_membre = ? AND receveur_id = ?', (int)$base, (int)$id);

		foreach ($r as $e)
		{
			if ($e->contenu)
			{
				$e->contenu = json_decode((string)$e->contenu, true);
			}
		}

		return empty($r)?false:$r;
	}

	public function hasDocs($base, $id)
	{
		$client = new Client;

		if ($base == 0) // Si c'est un client
		{
			if(!$client->get($id))
			{
				throw new UserException("Ce client n'existe pas.");
			}
		}
		else // Si c'est un membre de l'asso
		{
			throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
		}

		return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = ? AND receveur_id = ?', $base, $id);
	}

	public function listMoyensPaiement($assoc = false)
	{
		$db = DB::getInstance();

		$query = 'SELECT code, nom FROM plugin_facturation_paiement ORDER BY nom COLLATE NOCASE;';

		if ($assoc) {
			return $db->getAssoc($query);
		}
		else {
			return $db->getGrouped($query);
		}
	}

	public function getMoyenPaiement($code)
	{
		$db = DB::getInstance();
		return $db->firstColumn('SELECT nom FROM plugin_facturation_paiement WHERE code = ?;', $code);
	}

	public function delete($id)
	{
		return DB::getInstance()->delete('plugin_facturation_factures', 'id = '. (int)$id);
	}
}