# Refactor: route GetTileByLatLon through ITileService

**Task**: AZ-311_refactor_gettilebylatlon_via_tileservice
**Name**: GetTileByLatLon via ITileService.DownloadAndStoreSingleTileAsync
**Description**: Move single-tile download+insert logic from `/api/satellite/tiles/latlon` into `ITileService` and thin the endpoint handler.
**Complexity**: 2 points
**Dependencies**: AZ-310
**Component**: TileDownloader (currently in SatelliteProvider.Services)
**Tracker**: AZ-311
**Epic**: AZ-309

## Problem

`Program.cs:206` (`GetTileByLatLon`) injects `ISatelliteDownloader` and `ITileRepository` and re-implements download-then-insert. Same root cause as AZ-310 (architecture baseline F3).

## Outcome

- `GetTileByLatLon` handler is thin: call service, project to response, return.
- `TileService` owns the download+insert flow for single-tile requests.

## Scope

### Included
- New method `Task<TileMetadata> DownloadAndStoreSingleTileAsync(double latitude, double longitude, int zoomLevel, CancellationToken ct = default)` on `ITileService`.
- Implementation calls `ISatelliteDownloader.DownloadSingleTileAsync` then `ITileRepository.InsertAsync`.
- New unit tests for the new method.
- `Program.cs` `GetTileByLatLon` handler thinned.

### Excluded
- Changes to `DownloadTileResponse` shape.
- Changes to `ISatelliteDownloader` (zoom validation chain is unchanged).

## Acceptance Criteria

**AC-1: Endpoint contract preserved**
Given the API is running
When a client calls `GET /api/satellite/tiles/latlon?Latitude=...&Longitude=...&ZoomLevel=...`
Then the response is the same `DownloadTileResponse` JSON shape as before the refactor.

**AC-2: Service owns the flow**
Given valid lat/lon/zoom
When `DownloadAndStoreSingleTileAsync` is called
Then `ISatelliteDownloader.DownloadSingleTileAsync` is invoked once, `ITileRepository.InsertAsync` is invoked once, and the resulting `TileMetadata` is returned.

**AC-3: Endpoint no longer injects downloader/repo**
Given the post-refactor `Program.cs`
When the `GetTileByLatLon` handler is inspected
Then it injects `ITileService` and `ILogger<Program>` only (no `ISatelliteDownloader` or `ITileRepository`).

## Unit Tests

| AC Ref | What to Test | Required Outcome |
|--------|--------------|------------------|
| AC-2 | Happy path | Mocks for downloader + repo wired; assert one call each; assert TileMetadata fields match the downloaded tile |
| AC-2 | Downloader throws | Service propagates the exception; repo `InsertAsync` is not called |

## Constraints

- HTTP route, query parameter names, and response JSON shape preserved exactly.
- Zoom validation in `GoogleMapsDownloaderV2.DownloadSingleTileAsync` keeps firing.

## Risks & Mitigation

**Risk 1: TileMetadata projection drift**
- *Risk*: The endpoint currently constructs `DownloadTileResponse` directly from a `TileEntity`. After refactor it must do so from `TileMetadata`.
- *Mitigation*: Compare every field one-to-one in the new code; existing field-level mapping is straightforward.
