<?php

namespace idoit\Module\SyneticsJdisc\Controller;

use Exception;
use Idoit\Dto\Serialization\Serializer;
use Idoit\Dto\Validation\Validation;
use idoit\Module\SyneticsJdisc\Controller\Response\ArrayResponse;
use idoit\Module\SyneticsJdisc\Controller\Response\NotFoundResponse;
use idoit\Module\SyneticsJdisc\Controller\Table\SearchParams;
use idoit\Module\SyneticsJdisc\Graphql\Connector;
use idoit\Module\SyneticsJdisc\Graphql\GraphqlLoginException;
use idoit\Module\SyneticsJdisc\Model\JDiscServerDao;
use idoit\Module\SyneticsJdisc\Service\CertificateChecker;
use idoit\Module\SyneticsJdisc\SSLCertificateException;
use isys_application;
use isys_component_template;
use isys_component_template_language_manager;
use isys_exception_database;
use isys_jdisc_dao;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class DiscoveryServerController
{
    private ?JDiscServerDao $dao = null;

    public isys_component_template $template;
    public isys_component_template_language_manager $language;

    public function __construct() {
        $this->template = isys_application::instance()->container->get('template');
        $this->language = isys_application::instance()->container->get('language');
    }

    /**
     * @return JDiscServerDao
     * @throws \Exception
     */
    private function getDao(): JDiscServerDao
    {
        if (null === $this->dao) {
            $this->dao = isys_application::instance()->container->get('idoit.jdisc.server.dao');
        }
        return $this->dao;
    }

    /**
     * @param Request $request
     * @return Response
     * @throws isys_exception_database
     */
    public function find(Request $request): Response
    {
        $search = SearchParams::fromParams($request->get('params'));

        return new ArrayResponse(
            $this->getDao()->getData(null, $search),
            $this->getDao()->getCount(),
        );
    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function get(int $id): Response
    {
        $server = $this->getDao()->getConfiguration($id);
        if ($server === null) {
            return new NotFoundResponse('DiscoveryServer');
        }
        $server['name'] = $server['title'];
        unset($server['title']);
        return new JsonResponse($server);
    }

    /**
     * @param Request  $request
     * @param int|null $id
     * @return Response
     * @throws Exception
     */
    public function save(Request $request, ?int $id = null): Response
    {
        $server = $this->prepareServerData($request, $id);
        if ($server === null) {
            return new NotFoundResponse('DiscoveryServer');
        }

        $validationResponse = $this->validateServer($server);
        if ($validationResponse !== null) {
            return $validationResponse;
        }

        $server = $this->sanitizeServerData($server);

        $jdiscDao = $this->getDao()->getJdiscDao();
        try {
            $serverId = $jdiscDao->save(isys_jdisc_dao::C__CONFIGURATION, $server);
        } catch (Throwable $e) {
            return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
        if (empty($serverId)) {
            return new JsonResponse(['error' => 'Server save error'], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        if (!isset($server['default_server']) || (int) $server['default_server'] === 1) {
            $jdiscDao->reset_default_server($serverId);
        }

        return new JsonResponse(['id' => $serverId]);
    }

    /**
     * @param Request  $request
     * @param int|null $id
     * @return array|null
     * @throws Exception
     */
    private function prepareServerData(Request $request, ?int $id): ?array
    {
        $server = json_decode($request->getContent(), true);
        if (!empty($id)) {
            $serverData = $this->getDao()->getConfiguration($id);
            if ($serverData === null) {
                return null;
            }
            $server = array_merge($serverData, $server);
            $server['name'] = $server['name'] ?? $server['title'];
        } else {
            unset($server['id']);
        }
        return $server;
    }

    /**
     * @param array $serverData
     * @return Response|null
     */
    private function validateServer(array $serverData): ?JsonResponse
    {
        if (empty($serverData)) {
            return new JsonResponse(['error' => 'No data provided'], Response::HTTP_BAD_REQUEST);
        }

        try {
            $errors = Validation::validate(
                Serializer::fromJson(
                    \idoit\Module\SyneticsJdisc\Model\Dto\DiscoveryServer::class,
                    $serverData
                )
            );
            if (!empty($errors)) {
                $path = implode(' > ', $errors[0]->getPath());
                return new JsonResponse(
                    ['error' => "{$path}: {$errors[0]->getMessage()}"],
                    Response::HTTP_BAD_REQUEST
                );
            }
        } catch (Throwable $e) {
            return new JsonResponse(['error' => 'An unexpected error occurred during data validation.'], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return null;
    }

    /**
     * @param array $server
     * @return array
     */
    private function sanitizeServerData(array $server): array
    {
        if (isset($server['name'])) {
            $server['title'] = trim($server['name']);
            unset($server['name']);
        }

        $fieldsToSanitize = [
            'host',
            'database',
            'username',
            'discovery_username',
            'discovery_protocol'
        ];

        foreach ($fieldsToSanitize as $field) {
            if (isset($server[$field]) && is_string($server[$field])) {
                $server[$field] = trim($server[$field]);
            }
        }

        return $server;
    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function delete(int $id): Response
    {
        if ($this->getDao()->getConfiguration($id) === null) {
            return new NotFoundResponse('DiscoveryServer');
        }
        $result = $this->getDao()->delete([$id]);
        if (!$result) {
            return new JsonResponse(['error' => 'Server deletion error'], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
        return new JsonResponse(['success' => true]);
    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function checkConfig(int $id): Response
    {
        try {
            $this->getDao()->getJdiscDao()->get_connection($id);

            return new JsonResponse([
                'connection' => true,
                'message'    => $this->language->get('LC__MODULE__JDISC__CONNECTION_SUCCESS'),
            ]);
        } catch (Throwable $e) {
            isys_application::instance()->container->get('logger')->error(
                'JDisc DB connection check failed: ' . $e->getMessage()
            );

            return new JsonResponse([
                'connection' => false,
                'message'    => $e->getMessage(),
            ]);
        }
    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function checkDiscovery(int $id): Response
    {
        try {
            $jdiscDao = $this->getDao()->getJdiscModule();
            if ($jdiscDao->web_service_active($id) || $jdiscDao->isGraphqlAvailable($id)) {
                return new JsonResponse([
                    'success' => true,
                    'message' => $this->language->get('LC__MODULE__JDISC__DISCOVERY__CONNECTION_SUCCESS'),
                ]);
            }

            isys_application::instance()->container->get('logger')->warning(
                'JDisc discovery check failed: Both web_service_active() and isGraphqlAvailable() returned false.'
            );
        } catch (Throwable $e) {
            isys_application::instance()->container->get('logger')->error(
                'JDisc discovery (SOAP/GraphQL) connection check failed with exception: ' . $e->getMessage()
            );
        }

        return new JsonResponse([
            'success' => false,
            'message' => $this->language->get('LC__MODULE__JDISC__DISCOVERY__CONNECTION_FAILED'),
        ]);

    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function checkGraphql(int $id): Response
    {
        try {
            Connector::instance($id)->connect();

            return new JsonResponse([
                'success' => true,
                'message' => $this->language->get('LC__MODULE__JDISC__GRAPHQL__CONNECTION_SUCCESS')
            ]);
        } catch (GraphqlLoginException $e) {
            isys_application::instance()->container->get('logger')->error(
                'JDisc GraphQL login failed: ' . $e->getMessage()
            );

            return new JsonResponse([
                'success' => false,
                'message' => $e->getMessage()
            ]);
        } catch (Throwable $e) {
            isys_application::instance()->container->get('logger')->error(
                'JDisc GraphQL connection check failed with an unexpected error: ' . $e->getMessage()
            );

            return new JsonResponse([
                'success' => false,
                'message' => $this->language->get('LC__MODULE__JDISC__GRAPHQL__CONNECTION_UNEXPECTED_ERROR') . $e->getMessage()
            ]);
        }
    }

    /**
     * @param int $id
     * @return Response
     * @throws Exception
     */
    public function checkCertificate(int $id): Response
    {
        $response = new JsonResponse();

        $server = $this->getDao()->getConfiguration($id) ?? [];
        try {
            CertificateChecker::checkCertificate($server, $this->language);

            $response->setData(['valid' => true]);
        } catch (SSLCertificateException $e) {
            $response->setData(['warning' => $e->getMessage()]);
        } catch (\Throwable $e) {
            $response->setData(['error' => $e->getMessage()]);
            $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return $response;
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function getFiles(Request $request): Response
    {
        try {
            $queryParameters = $request->query->all();
            $searchTerm = $queryParameters['q'] ?? null;
            $ids = $queryParameters['ids'] ?? null;

            $files = $this->getDao()->getFiles($searchTerm, $ids);
            return new JsonResponse($files);
        } catch (Throwable $e) {
            return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }
}
