import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  Query,
  UseGuards,
  Request,
  HttpCode,
  HttpStatus,
  NotFoundException,
  ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { RolesGuard, Roles } from '@it0/common';
import { TenantTag } from '../../../domain/entities/tenant-tag.entity';
import { TenantTagAssignment } from '../../../domain/entities/tenant-tag-assignment.entity';

/**
 * Platform admin API for managing tenant tags.
 * Tags can be created, applied to tenants, and used as notification targeting criteria.
 */
@Controller('api/v1/admin/tenant-tags')
@UseGuards(RolesGuard)
export class TenantTagController {
  constructor(
    @InjectRepository(TenantTag)
    private readonly tagRepo: Repository<TenantTag>,
    @InjectRepository(TenantTagAssignment)
    private readonly assignRepo: Repository<TenantTagAssignment>,
    private readonly dataSource: DataSource,
  ) {}

  // ── Tag CRUD ──────────────────────────────────────────────────────────────

  /** GET /api/v1/admin/tenant-tags — list all tags with tenant counts */
  @Get()
  @Roles('platform_admin', 'platform_super_admin')
  async listTags() {
    const tags = await this.tagRepo.find({ order: { createdAt: 'DESC' } });
    // Get tenant counts per tag
    const counts = await this.dataSource.query(
      `SELECT tag_id, COUNT(*)::int AS cnt
       FROM public.tenant_tag_assignments
       GROUP BY tag_id`,
    );
    const countMap = Object.fromEntries(counts.map((r: any) => [r.tag_id, r.cnt]));
    return tags.map((t) => ({ ...t, tenantCount: countMap[t.id] ?? 0 }));
  }

  /** POST /api/v1/admin/tenant-tags — create a tag */
  @Post()
  @Roles('platform_admin', 'platform_super_admin')
  async createTag(@Body() body: { name: string; color?: string; description?: string }, @Request() req: any) {
    const existing = await this.tagRepo.findOne({ where: { name: body.name } });
    if (existing) throw new ConflictException(`Tag "${body.name}" already exists`);

    const tag = this.tagRepo.create({
      name: body.name.trim(),
      color: body.color ?? '#6366F1',
      description: body.description ?? null,
      createdBy: req.user?.id ?? null,
    });
    return this.tagRepo.save(tag);
  }

  /** PUT /api/v1/admin/tenant-tags/:id — update a tag */
  @Put(':id')
  @Roles('platform_admin', 'platform_super_admin')
  async updateTag(@Param('id') id: string, @Body() body: { name?: string; color?: string; description?: string }) {
    const tag = await this.tagRepo.findOne({ where: { id } });
    if (!tag) throw new NotFoundException('Tag not found');
    if (body.name !== undefined) tag.name = body.name.trim();
    if (body.color !== undefined) tag.color = body.color;
    if (body.description !== undefined) tag.description = body.description;
    return this.tagRepo.save(tag);
  }

  /** DELETE /api/v1/admin/tenant-tags/:id — delete a tag (removes all assignments) */
  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  @Roles('platform_admin', 'platform_super_admin')
  async deleteTag(@Param('id') id: string) {
    const tag = await this.tagRepo.findOne({ where: { id } });
    if (!tag) throw new NotFoundException('Tag not found');
    await this.tagRepo.remove(tag);
  }

  // ── Tag Assignments ───────────────────────────────────────────────────────

  /** GET /api/v1/admin/tenant-tags/tenants/:tenantId — get tags of a tenant */
  @Get('tenants/:tenantId')
  @Roles('platform_admin', 'platform_super_admin')
  async getTagsForTenant(@Param('tenantId') tenantId: string) {
    const rows = await this.dataSource.query(
      `SELECT t.*, a.tagged_by, a.tagged_at
       FROM public.tenant_tags t
       JOIN public.tenant_tag_assignments a ON a.tag_id = t.id
       WHERE a.tenant_id = $1
       ORDER BY a.tagged_at DESC`,
      [tenantId],
    );
    return rows;
  }

  /** GET /api/v1/admin/tenant-tags/:id/tenants — get tenants with a given tag */
  @Get(':id/tenants')
  @Roles('platform_admin', 'platform_super_admin')
  async getTenantsForTag(
    @Param('id') tagId: string,
    @Query('limit') limit = '100',
    @Query('offset') offset = '0',
  ) {
    const tag = await this.tagRepo.findOne({ where: { id: tagId } });
    if (!tag) throw new NotFoundException('Tag not found');

    const [rows, count] = await Promise.all([
      this.dataSource.query(
        `SELECT t.id, t.name, t.slug, t.plan, t.status, a.tagged_at
         FROM public.tenants t
         JOIN public.tenant_tag_assignments a ON a.tenant_id = t.id
         WHERE a.tag_id = $1
         ORDER BY a.tagged_at DESC
         LIMIT $2 OFFSET $3`,
        [tagId, parseInt(limit), parseInt(offset)],
      ),
      this.dataSource.query(
        `SELECT COUNT(*)::int AS total FROM public.tenant_tag_assignments WHERE tag_id = $1`,
        [tagId],
      ),
    ]);
    return { items: rows, total: count[0]?.total ?? 0 };
  }

  /** POST /api/v1/admin/tenant-tags/:id/assign — assign tag to tenants */
  @Post(':id/assign')
  @Roles('platform_admin', 'platform_super_admin')
  async assignTag(
    @Param('id') tagId: string,
    @Body() body: { tenantIds: string[] },
    @Request() req: any,
  ) {
    const tag = await this.tagRepo.findOne({ where: { id: tagId } });
    if (!tag) throw new NotFoundException('Tag not found');

    for (const tenantId of body.tenantIds ?? []) {
      await this.dataSource.query(
        `INSERT INTO public.tenant_tag_assignments (tenant_id, tag_id, tagged_by)
         VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
        [tenantId, tagId, req.user?.id ?? null],
      );
    }
    return { ok: true, assigned: body.tenantIds?.length ?? 0 };
  }

  /** DELETE /api/v1/admin/tenant-tags/:id/assign/:tenantId — remove tag from tenant */
  @Delete(':id/assign/:tenantId')
  @HttpCode(HttpStatus.NO_CONTENT)
  @Roles('platform_admin', 'platform_super_admin')
  async removeAssignment(@Param('id') tagId: string, @Param('tenantId') tenantId: string) {
    await this.dataSource.query(
      `DELETE FROM public.tenant_tag_assignments WHERE tag_id = $1 AND tenant_id = $2`,
      [tagId, tenantId],
    );
  }

  /** POST /api/v1/admin/tenant-tags/tenants/:tenantId/tags — set all tags for a tenant */
  @Post('tenants/:tenantId/tags')
  @Roles('platform_admin', 'platform_super_admin')
  async setTenantTags(
    @Param('tenantId') tenantId: string,
    @Body() body: { tagIds: string[] },
    @Request() req: any,
  ) {
    // Remove existing assignments
    await this.dataSource.query(
      `DELETE FROM public.tenant_tag_assignments WHERE tenant_id = $1`,
      [tenantId],
    );
    // Insert new assignments
    for (const tagId of body.tagIds ?? []) {
      await this.dataSource.query(
        `INSERT INTO public.tenant_tag_assignments (tenant_id, tag_id, tagged_by)
         VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
        [tenantId, tagId, req.user?.id ?? null],
      );
    }
    return { ok: true };
  }
}
