<?php

namespace App\Controller;

use App\Form\CityToolType;
use App\Form\OsmoseToolType;
use App\Service\OsmoseClient;
use App\Service\OverpassClient;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/tools')]
class ToolsController extends AbstractController
{
    #[Route('/', name: 'app_tools')]
    public function index(): Response
    {
        return $this->render('tools/index.html.twig', [
        ]);
    }

    #[Route('/city', name: 'app_tools_city')]
    public function city(
        Request $request,
        OverpassClient $overpass,
    ): Response {
        $form = $this->createForm(CityToolType::class, []);
        $form->add('submit', SubmitType::class, [
            'label' => 'button.generate',
        ]);

        $form->handleRequest($request);
        if ($form->isSubmitted() and $form->isValid()) {
            $areaId = $form->get('area')->getData();
            $query = sprintf('relation["boundary"="administrative"]["admin_level"="8"]["name"](area:%d);', 3600000000 + $areaId); // Cf. <https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#By_area_.28area.29>
            $json = $overpass->query($query);

            $response = new StreamedResponse();

            $response->headers->set('Content-Type', 'text/csv');
            $response->headers->set(
                'Content-Disposition',
                HeaderUtils::makeDisposition(
                    HeaderUtils::DISPOSITION_ATTACHMENT,
                    sprintf('cities-in-%d.csv', $areaId)
                )
            );

            $response->setCallback(function () use ($json): void {
                $headings = [
                    'name',
                    'description',
                    'osm',
                    'geojson',
                    'status',
                ];

                $csv = fopen('php://output', 'a');

                fputcsv($csv, $headings);

                $osm = \OSM\OSM::createFromJson($json);

                foreach ($osm->elements as $relation) {
                    $name = $relation->getTagValue('name');

                    $feature = new \GeoJson\Feature\Feature(
                        \OSM\GeoJsonConverter::convertRelationToPolygon($relation),
                        ['name' => $name]
                    );

                    fputcsv($csv, [
                        $name,
                        $name,
                        '',
                        json_encode(new \GeoJson\Feature\FeatureCollection([$feature])),
                        'todo',
                    ]);
                }

                fclose($csv);
            });

            return $response;
        }

        return $this->render('tools/city.html.twig', [
            'form' => $form,
        ]);
    }

    #[Route('/osmose', name: 'app_tools_osmose')]
    public function osmose(
        Request $request,
        OsmoseClient $osmose,
        OverpassClient $overpass,
        LoggerInterface $logger,
    ): Response {
        $form = $this->createForm(OsmoseToolType::class, array_merge(['limit' => 500], $request->query->all()));
        $form->add('submit', SubmitType::class, [
            'label' => 'button.generate',
        ]);

        $form->handleRequest($request);
        if ($form->isSubmitted() and $form->isValid()) {
            $issues = $osmose->issues([
                'item' => $form->get('item')->getData(),
                'source' => $form->get('source')->getData(),
                'class' => $form->get('class')->getData(),
                'limit' => $form->get('limit')->getData(),
                'username' => '',
                'bbox' => '',
                'full' => 'true',
            ]);

            $response = new StreamedResponse();

            $response->headers->set('Content-Type', 'text/csv');
            $response->headers->set(
                'Content-Disposition',
                HeaderUtils::makeDisposition(
                    HeaderUtils::DISPOSITION_ATTACHMENT,
                    'issues.csv'
                )
            );

            $issuesGeojson = \GeoJson\GeoJson::jsonUnserialize(json_decode($issues, true));

            $logger->debug(__METHOD__.' osmose issues', [
                count($issuesGeojson->getFeatures()),
            ]);

            $groupByCity = (bool) $form->get('group_by_city')->getData();
            if ($groupByCity) {
                $minLat = null;
                $maxLat = null;
                $minLon = null;
                $maxLon = null;

                foreach ($issuesGeojson->getFeatures() as $feature) {
                    $geometry = $feature->getGeometry();
                    $coordinates = $geometry->getCoordinates();
                    $lat = (float) $coordinates[1];
                    $lon = (float) $coordinates[0];
                    if (is_null($minLat) or ($lat < $minLat)) {
                        $minLat = $lat;
                    }
                    if (is_null($maxLat) or ($lat > $maxLat)) {
                        $maxLat = $lat;
                    }
                    if (is_null($minLon) or ($lon < $minLon)) {
                        $minLon = $lon;
                    }
                    if (is_null($maxLon) or ($lon > $maxLon)) {
                        $maxLon = $lon;
                    }
                }

                $citiesData = $overpass->query(sprintf('relation[admin_level=8][boundary=administrative]["ref:INSEE"](%f,%f,%f,%f);', $minLat, $minLon, $maxLat, $maxLon));
                $citiesOsm = \OSM\OSM::createFromJson($citiesData);

                $logger->debug(__METHOD__.' overpass cities', [
                    count($citiesOsm->elements),
                ]);

                $cities = [];
                foreach ($citiesOsm->elements as $cityOsm) {
                    $geojson = \OSM\GeoJsonConverter::convertRelationToPolygon($cityOsm);
                    $geojsonString = '{"type":"FeatureCollection","features":[{"type": "Feature","properties":{},"geometry":'.json_encode($geojson->jsonSerialize()).'}]}';
                    $polygon = \geoPHP::load($geojsonString, 'json');
                    $cities[] = [
                        'name' => $cityOsm->getTagValue('name'),
                        'polygon' => $polygon,
                        'geojson' => $geojsonString,
                        'features' => [],
                    ];
                }

                foreach ($issuesGeojson->getFeatures() as $feature) {
                    $point = \geoPHP::load(json_encode($feature->jsonSerialize()), 'json');
                    foreach ($cities as $cityIndex => $city) {
                        $isIn = $city['polygon']->contains($point);
                        if ($isIn) {
                            $cities[$cityIndex]['features'][] = $feature;
                        }
                    }
                }

                $cities = array_filter($cities, function ($city) { return !empty($city['features']); });

                $logger->debug(__METHOD__.' cities', [
                    count($cities),
                ]);

                if (empty($cities)) {
                    throw new \RuntimeException(sprintf('Problème improbable : les %d signalements n’ont pas pu être groupés dans les %d communes', count($issuesGeojson->getFeatures()), count($citiesOsm->elements)));
                }

                $response->setCallback(function () use ($cities): void {
                    $headings = [
                        'name',
                        'description',
                        'osm',
                        'geojson',
                        'status',
                    ];

                    $csv = fopen('php://output', 'a');

                    fputcsv($csv, $headings);

                    foreach ($cities as $city) {
                        $name = $city['name'];
                        $description = sprintf('**%d** signalements sur la commune **%s** :'.PHP_EOL.PHP_EOL, count($city['features']), $city['name']);
                        $geojson = $city['geojson'];

                        $nodes = [];
                        foreach ($city['features'] as $featureIndex => $feature) {
                            $properties = $feature->getProperties();
                            $geometry = $feature->getGeometry();
                            $coordinates = $geometry->getCoordinates();
                            $description .= sprintf(
                                '* Signalement [%s](https://osmose.openstreetmap.fr/fr/map/#issue_uuid=%s&loc=12/%f/%f) %s'.PHP_EOL,
                                $properties['uuid'],
                                $properties['uuid'],
                                (float) $coordinates[1],
                                (float) $coordinates[0],
                                $properties['title']
                            );

                            if (isset($properties['fixes'])) {
                                $tags = [];
                                foreach ($properties['fixes'] as $fixItems) {
                                    foreach ($fixItems as $fixItem) {
                                        $isExpected = (
                                            ('N' === $fixItem['type'])
                                            and array_key_exists('create', $fixItem)
                                        );
                                        if (!$isExpected) {
                                            continue;
                                        } // TODO gérer tous les cas possibles ici
                                        foreach ($fixItem['create'] as $k => $v) {
                                            $tags[$k] = $v;
                                        }
                                    }
                                }

                                $geometry = $feature->getGeometry();
                                $coordinates = $geometry->getCoordinates();
                                $nodes[] = [
                                    'type' => 'node',
                                    'id' => -1 * ($featureIndex + 1),
                                    'lat' => (float) $coordinates[1],
                                    'lon' => (float) $coordinates[0],
                                    'tags' => $tags,
                                ];
                            }
                        }
                        if (!empty($nodes)) {
                            $osm = \OSM\OSM::createFromArray([
                                'elements' => $nodes,
                            ]);
                        } else {
                            $osm = '';
                        }

                        fputcsv($csv, [
                            $name,
                            $description,
                            $osm,
                            $geojson,
                            'todo',
                        ]);
                    }

                    fclose($csv);
                });
            } else {
                $response->setCallback(function () use ($issuesGeojson): void {
                    $headings = [
                        'name',
                        'description',
                        'osm',
                        'geojson',
                        'status',
                    ];

                    $csv = fopen('php://output', 'a');

                    fputcsv($csv, $headings);

                    $total = $issuesGeojson->count();
                    foreach ($issuesGeojson->getFeatures() as $index => $feature) {
                        $properties = $feature->getProperties();
                        $name = sprintf('%s n°%d/%d', $properties['title'], $index + 1, $total);
                        $description = <<<EOT
### Signalement {$properties['uuid']}

* Niveau {$properties['level']}
* Thème {$properties['item']}
* Identifiant de source {$properties['source_id']}
* Identifiant de classe {$properties['class']}

### Osmose

[✔ Fait](https://osmose.openstreetmap.fr/api/0.3/issue/{$properties['uuid']}/done)
[✘ Faux positif](https://osmose.openstreetmap.fr/api/0.3/issue/{$properties['uuid']}/false)
EOT;
                        if (isset($properties['fixes'])) {
                            $tags = [];
                            foreach ($properties['fixes'] as $fixItems) {
                                foreach ($fixItems as $fixItem) {
                                    $isExpected = (
                                        ('N' === $fixItem['type'])
                                        and array_key_exists('create', $fixItem)
                                    );
                                    if (!$isExpected) {
                                        continue;
                                    } // TODO gérer tous les cas possibles ici
                                    foreach ($fixItem['create'] as $k => $v) {
                                        $tags[$k] = $v;
                                    }
                                }
                            }

                            $geometry = $feature->getGeometry();
                            $coordinates = $geometry->getCoordinates();
                            $osm = \OSM\OSM::createFromArray([
                                'elements' => [[
                                    'type' => 'node',
                                    'id' => -1,
                                    'lat' => (float) $coordinates[1],
                                    'lon' => (float) $coordinates[0],
                                    'tags' => $tags,
                                ]],
                            ]);
                        } else {
                            $osm = '';
                        }

                        fputcsv($csv, [
                            $name,
                            $description,
                            $osm,
                            json_encode($feature->jsonSerialize()),
                            'todo',
                        ]);
                    }

                    fclose($csv);
                });
            }

            return $response;
        }

        return $this->render('tools/osmose.html.twig', [
            'form' => $form,
        ]);
    }
}
