import {
  Controller,
  Get,
  Post,
  Patch,
  Param,
  Body,
  Query,
  HttpCode,
  HttpStatus,
  Logger,
  UsePipes,
  ValidationPipe,
  NotFoundException,
  ParseUUIDPipe,
  ParseIntPipe,
  DefaultValuePipe,
  UseGuards,
  ForbiddenException,
  Inject,
} from '@nestjs/common';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBadRequestResponse,
  ApiNotFoundResponse,
  ApiQuery,
  ApiParam,
  ApiBearerAuth,
} from '@nestjs/swagger';
import {
  CreateOrganizationDto,
  UpdateOrganizationDto,
  OrganizationResponseDto,
  OrganizationListResponseDto,
} from '../dto/organization.dto';
import { OrganizationMapper } from '../mappers/organization.mapper';
import {
  OrganizationRepository,
  ORGANIZATION_REPOSITORY,
} from '@domain/ports/out/organization.repository';
import { Organization, OrganizationType } from '@domain/entities/organization.entity';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
import { Roles } from '../decorators/roles.decorator';
import { v4 as uuidv4 } from 'uuid';

/**
 * Organizations Controller
 *
 * Manages organization CRUD operations:
 * - Create organization (admin only)
 * - Get organization details
 * - Update organization (admin/manager)
 * - List organizations
 */
@ApiTags('Organizations')
@Controller('organizations')
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth()
export class OrganizationsController {
  private readonly logger = new Logger(OrganizationsController.name);

  constructor(
    @Inject(ORGANIZATION_REPOSITORY) private readonly organizationRepository: OrganizationRepository
  ) {}

  /**
   * Create a new organization
   *
   * Admin-only endpoint to create a new organization.
   */
  @Post()
  @HttpCode(HttpStatus.CREATED)
  @Roles('admin')
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Create new organization',
    description: 'Create a new organization (freight forwarder, carrier, or shipper). Admin-only.',
  })
  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'Organization created successfully',
    type: OrganizationResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - requires admin role',
  })
  @ApiBadRequestResponse({
    description: 'Invalid request parameters',
  })
  async createOrganization(
    @Body() dto: CreateOrganizationDto,
    @CurrentUser() user: UserPayload
  ): Promise<OrganizationResponseDto> {
    this.logger.log(`[Admin: ${user.email}] Creating organization: ${dto.name} (${dto.type})`);

    try {
      // Check for duplicate name
      const existingByName = await this.organizationRepository.findByName(dto.name);
      if (existingByName) {
        throw new ForbiddenException(`Organization with name "${dto.name}" already exists`);
      }

      // Check for duplicate SCAC if provided
      if (dto.scac) {
        const existingBySCAC = await this.organizationRepository.findBySCAC(dto.scac);
        if (existingBySCAC) {
          throw new ForbiddenException(`Organization with SCAC "${dto.scac}" already exists`);
        }
      }

      // Create organization entity
      const organization = Organization.create({
        id: uuidv4(),
        name: dto.name,
        type: dto.type,
        scac: dto.scac,
        siren: dto.siren,
        siret: dto.siret,
        eori: dto.eori,
        contact_phone: dto.contact_phone,
        contact_email: dto.contact_email,
        address: OrganizationMapper.mapDtoToAddress(dto.address),
        logoUrl: dto.logoUrl,
        documents: [],
        isActive: true,
      });

      // Save to database
      const savedOrg = await this.organizationRepository.save(organization);

      this.logger.log(`Organization created successfully: ${savedOrg.name} (${savedOrg.id})`);

      return OrganizationMapper.toDto(savedOrg);
    } catch (error: any) {
      this.logger.error(
        `Organization creation failed: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Get organization by ID
   *
   * Retrieve details of a specific organization.
   * Users can only view their own organization unless they are admins.
   */
  @Get(':id')
  @ApiOperation({
    summary: 'Get organization by ID',
    description:
      'Retrieve organization details. Users can view their own organization, admins can view any.',
  })
  @ApiParam({
    name: 'id',
    description: 'Organization ID (UUID)',
    example: '550e8400-e29b-41d4-a716-446655440000',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Organization details retrieved successfully',
    type: OrganizationResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiNotFoundResponse({
    description: 'Organization not found',
  })
  async getOrganization(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() user: UserPayload
  ): Promise<OrganizationResponseDto> {
    this.logger.log(`[User: ${user.email}] Fetching organization: ${id}`);

    const organization = await this.organizationRepository.findById(id);
    if (!organization) {
      throw new NotFoundException(`Organization ${id} not found`);
    }

    // Authorization: Users can only view their own organization (unless admin)
    if (user.role !== 'ADMIN' && organization.id !== user.organizationId) {
      throw new ForbiddenException('You can only view your own organization');
    }

    return OrganizationMapper.toDto(organization);
  }

  /**
   * Update organization
   *
   * Update organization details (name, address, logo, status).
   * Requires admin or manager role.
   */
  @Patch(':id')
  @Roles('admin', 'manager')
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Update organization',
    description:
      'Update organization details (name, address, logo, status). Requires admin or manager role.',
  })
  @ApiParam({
    name: 'id',
    description: 'Organization ID (UUID)',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Organization updated successfully',
    type: OrganizationResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - requires admin or manager role',
  })
  @ApiNotFoundResponse({
    description: 'Organization not found',
  })
  async updateOrganization(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: UpdateOrganizationDto,
    @CurrentUser() user: UserPayload
  ): Promise<OrganizationResponseDto> {
    this.logger.log(`[User: ${user.email}] Updating organization: ${id}`);

    const organization = await this.organizationRepository.findById(id);
    if (!organization) {
      throw new NotFoundException(`Organization ${id} not found`);
    }

    // Authorization: Managers can only update their own organization
    if (user.role === 'manager' && organization.id !== user.organizationId) {
      throw new ForbiddenException('You can only update your own organization');
    }

    // Update fields
    if (dto.name) {
      organization.updateName(dto.name);
    }

    if (dto.siren) {
      organization.updateSiren(dto.siren);
    }

    if (dto.siret) {
      organization.updateSiret(dto.siret);
    }

    if (dto.eori) {
      organization.updateEori(dto.eori);
    }

    if (dto.contact_phone) {
      organization.updateContactPhone(dto.contact_phone);
    }

    if (dto.contact_email) {
      organization.updateContactEmail(dto.contact_email);
    }

    if (dto.address) {
      organization.updateAddress(OrganizationMapper.mapDtoToAddress(dto.address));
    }

    if (dto.logoUrl !== undefined) {
      organization.updateLogoUrl(dto.logoUrl);
    }

    if (dto.isActive !== undefined) {
      if (dto.isActive) {
        organization.activate();
      } else {
        organization.deactivate();
      }
    }

    // Save updated organization
    const updatedOrg = await this.organizationRepository.save(organization);

    this.logger.log(`Organization updated successfully: ${updatedOrg.id}`);

    return OrganizationMapper.toDto(updatedOrg);
  }

  /**
   * List organizations
   *
   * Retrieve a paginated list of organizations.
   * Admins can see all, others see only their own.
   */
  @Get()
  @ApiOperation({
    summary: 'List organizations',
    description:
      'Retrieve a paginated list of organizations. Admins see all, others see only their own.',
  })
  @ApiQuery({
    name: 'page',
    required: false,
    description: 'Page number (1-based)',
    example: 1,
  })
  @ApiQuery({
    name: 'pageSize',
    required: false,
    description: 'Number of items per page',
    example: 20,
  })
  @ApiQuery({
    name: 'type',
    required: false,
    description: 'Filter by organization type',
    enum: OrganizationType,
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Organizations list retrieved successfully',
    type: OrganizationListResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  async listOrganizations(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('pageSize', new DefaultValuePipe(20), ParseIntPipe) pageSize: number,
    @Query('type') type: OrganizationType | undefined,
    @CurrentUser() user: UserPayload
  ): Promise<OrganizationListResponseDto> {
    this.logger.log(
      `[User: ${user.email}] Listing organizations: page=${page}, pageSize=${pageSize}, type=${type}`
    );

    // Fetch organizations
    let organizations: Organization[];

    if (user.role === 'ADMIN') {
      // Admins can see all organizations
      organizations = await this.organizationRepository.findAll();
    } else {
      // Others see only their own organization
      const userOrg = await this.organizationRepository.findById(user.organizationId);
      organizations = userOrg ? [userOrg] : [];
    }

    // Filter by type if provided
    const filteredOrgs = type ? organizations.filter(org => org.type === type) : organizations;

    // Paginate
    const startIndex = (page - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    const paginatedOrgs = filteredOrgs.slice(startIndex, endIndex);

    // Convert to DTOs
    const orgDtos = OrganizationMapper.toDtoArray(paginatedOrgs);

    const totalPages = Math.ceil(filteredOrgs.length / pageSize);

    return {
      organizations: orgDtos,
      total: filteredOrgs.length,
      page,
      pageSize,
      totalPages,
    };
  }
}
