import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Query,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import { TripStatus } from '../common/constants/enums';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import type { JwtUserPayload } from '../auth/jwt.types';
import { GpsService } from '../gps/gps.service';
import { PunchesService } from '../punches/punches.service';
import { TrackingCoverageService } from '../tracking/tracking-coverage.service';
import { TrackingEventsService } from '../tracking/tracking-events.service';
import { TripsService } from './trips.service';
import {
  normalizeArrivalPunch,
  normalizeCreateTripInput,
  normalizeDeparturePunch,
  normalizeGpsBatch,
  normalizeMeetingEndPunch,
  normalizeMeetingStartPunch,
  normalizeTrackingEventsBatch,
  toClientTripDetail,
  toClientTripSummary,
} from './trip-api.mapper';
import { AddNextClientDto } from './dto/add-next-client.dto';
import { TrackingEventsBatchDto } from '../tracking/dto/tracking-event.dto';

/**
 * Legacy Flutter routes — maps to trips + gps + punches modules.
 */
@ApiTags('travel-requests (legacy)')
@ApiBearerAuth('access-token')
@Controller('travel-requests')
export class TravelRequestsController {
  constructor(
    private readonly trips: TripsService,
    private readonly gps: GpsService,
    private readonly punches: PunchesService,
    private readonly trackingCoverage: TrackingCoverageService,
    private readonly trackingEvents: TrackingEventsService,
  ) {}

  private async loadTripDetail(tripId: string, actor: JwtUserPayload) {
    const trip = await this.trips.getOne(tripId, actor);
    const route = await this.gps.listRoute(tripId, { limit: 5000 });
    let trackingCoverageSummary: Record<string, unknown> | null = null;
    try {
      trackingCoverageSummary =
        await this.trackingCoverage.getSummaryForTrip(tripId);
    } catch {
      trackingCoverageSummary = null;
    }
    return toClientTripDetail(
      trip as unknown as Record<string, unknown>,
      route.items as unknown as Record<string, unknown>[],
      trackingCoverageSummary as Record<string, unknown> | null,
    );
  }

  @Get('active')
  async getActive(@CurrentUser() actor: JwtUserPayload) {
    const active = await this.trips.getActiveTrip(actor);
    return this.loadTripDetail(String(active.tripId), actor);
  }

  @Get('summary')
  async summary(
    @CurrentUser() actor: JwtUserPayload,
    @Query('mine') mine?: string,
  ) {
    return this.trips.summary(actor, mine !== 'false');
  }

  @Post()
  async create(
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    const trip = await this.trips.create(
      actor,
      normalizeCreateTripInput(body),
    );
    return toClientTripSummary(trip as unknown as Record<string, unknown>);
  }

  @Get()
  async list(
    @CurrentUser() actor: JwtUserPayload,
    @Query('mine') mine?: string,
    @Query('page') page?: string,
    @Query('userId') userId?: string,
    @Query('status') status?: TripStatus,
    @Query('from') from?: string,
    @Query('to') to?: string,
    @Query('cursor') cursor?: string,
    @Query('limit') limit?: string,
    @Query('paginated') paginated?: string,
  ) {
    const hasPage = page != null || limit != null;
    if (hasPage) {
      const paged = await this.trips.paginateTravelRequests(actor, {
        mine: mine !== 'false',
        page: page ? Number(page) : 1,
        limit: limit ? Number(limit) : 20,
        userId,
      });
      return {
        items: paged.items.map((t) =>
          toClientTripSummary(t as unknown as Record<string, unknown>),
        ),
        meta: paged.meta,
      };
    }

    const result = await this.trips.list(actor, {
      userId,
      status,
      from,
      to,
      cursor,
      limit: limit ? Number(limit) : undefined,
    });

    const items = await Promise.all(
      result.items.map(async (t) => {
        const full = await this.trips.findByTripId(String(t.tripId));
        return toClientTripSummary(full as unknown as Record<string, unknown>);
      }),
    );

    if (paginated === '1' || paginated === 'true' || cursor) {
      return { items, nextCursor: result.nextCursor };
    }

    return items;
  }

  @Get(':tripId/route-points')
  async routePoints(@Param('tripId') tripId: string) {
    const route = await this.gps.listRoute(tripId, { limit: 5000 });
    return route.items;
  }

  @Get(':tripId/tracking-coverage')
  async getTrackingCoverage(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Query('legId') legId?: string,
    @Query('includeEvents') includeEvents?: string,
  ) {
    return this.trackingCoverage.getCoverageReport(
      requestId,
      actor,
      legId,
      includeEvents === '1' || includeEvents === 'true',
    );
  }

  @Get(':tripId')
  async getOne(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
  ) {
    return this.loadTripDetail(requestId, actor);
  }

  @Patch(':tripId')
  async update(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    if (
      Array.isArray(body.tripLegs) ||
      body.vehicleType != null ||
      body.fuelType !== undefined
    ) {
      const trip = await this.trips.updateFromLegacyPatch(requestId, actor, body);
      return toClientTripSummary(trip as unknown as Record<string, unknown>);
    }

    const trip = await this.trips.update(requestId, actor, {
      fromLocation: body.fromLocation ? String(body.fromLocation) : undefined,
      toLocation: body.toLocation ? String(body.toLocation) : undefined,
      clientName: body.clientName ? String(body.clientName) : undefined,
      purpose: body.purpose ? String(body.purpose) : undefined,
    });
    return toClientTripSummary(trip as unknown as Record<string, unknown>);
  }

  @Delete(':tripId')
  async remove(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
  ) {
    return this.trips.remove(requestId, actor);
  }

  @Post(':tripId/next-client')
  async addNextClient(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: AddNextClientDto,
  ) {
    await this.trips.addNextClient(requestId, actor, body);
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/return-start')
  async returnStart(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    await this.trips.startReturnTrip(requestId, actor, {
      timestamp: body.timestamp ? String(body.timestamp) : undefined,
      latitude:
        body.latitude != null
          ? Number(body.latitude)
          : body.lat != null
            ? Number(body.lat)
            : undefined,
      longitude:
        body.longitude != null
          ? Number(body.longitude)
          : body.lng != null
            ? Number(body.lng)
            : undefined,
    });
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/meeting-start')
  async meetingStart(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    await this.punches.recordMeetingStart(
      requestId,
      actor,
      normalizeMeetingStartPunch(body),
    );
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/meeting-end')
  async meetingEnd(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    await this.punches.recordMeetingEnd(
      requestId,
      actor,
      normalizeMeetingEndPunch(body),
    );
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/departure')
  async departure(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    await this.punches.recordDeparture(
      requestId,
      actor,
      normalizeDeparturePunch(body),
    );
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/arrival')
  async arrival(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    await this.punches.recordArrival(
      requestId,
      actor,
      normalizeArrivalPunch(body),
    );
    return this.loadTripDetail(requestId, actor);
  }

  @Post(':tripId/route-points/batch')
  @Throttle({ default: { limit: 60, ttl: 60000 } })
  routePointsBatch(
    @Param('tripId') tripId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    return this.gps.ingestBatch(tripId, actor, normalizeGpsBatch(body));
  }

  @Post(':tripId/tracking-events/batch')
  @Throttle({ default: { limit: 60, ttl: 60000 } })
  async trackingEventsBatch(
    @Param('tripId') requestId: string,
    @CurrentUser() actor: JwtUserPayload,
    @Body() body: Record<string, unknown>,
  ) {
    const normalized = normalizeTrackingEventsBatch(body);
    return this.trackingEvents.ingestBatch(
      requestId,
      actor,
      normalized as TrackingEventsBatchDto,
    );
  }
}
