import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import {
  Entity, PrimaryGeneratedColumn, Column,
  CreateDateColumn, UpdateDateColumn,
} from 'typeorm';
import {
  Injectable, UnauthorizedException, ConflictException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import {
  Controller, Post, Patch, Body, Get, UseGuards, Request, HttpCode, HttpStatus,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import {
  IsEmail, IsString, MinLength, IsOptional,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

// ============================================================
// ENTITIES
// ============================================================

@Entity('user_tenant_memberships')
export class UserTenantMembership {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ length: 80 }) email: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ default: 'owner' }) role: string;
  @Column({ default: 'active' }) status: string;
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
}

@Entity('tenants')
export class Tenant {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ length: 150 }) name: string;
  @Column({ length: 14, unique: true }) document: string;
  @Column({ length: 80 }) email: string;
  @Column({ nullable: true }) phone: string;
  @Column({ name: 'municipal_inscription', nullable: true }) municipalInscription: string;
  @Column({ name: 'ibge_code', nullable: true }) ibgeCode: string;
  @Column({ nullable: true }) uf: string;
  @Column({ name: 'tax_regime', type: 'smallint', default: 1 }) taxRegime: number;
  @Column({ name: 'simple_national', default: false }) simpleNational: boolean;
  @Column({ name: 'fiscal_incentive', default: false }) fiscalIncentive: boolean;
  @Column({ name: 'autentique_token', nullable: true }) autentiqueToken: string;
  @Column({ name: 'nfse_url', nullable: true }) nfseUrl: string;
  @Column({ name: 'nfse_certificate', type: 'text', nullable: true }) nfseCertificate: string;
  @Column({ name: 'nfse_certificate_pass', nullable: true }) nfseCertificatePass: string;
  @Column({ default: 'trial' }) plan: string;
  @Column({ default: 'active' }) status: string;
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
}

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid') id: string;
  @Column({ name: 'tenant_id' }) tenantId: string;
  @Column({ name: 'role_id', nullable: true }) roleId: string;
  @Column({ length: 150 }) name: string;
  @Column({ length: 80 }) email: string;
  @Column({ name: 'password_hash' }) passwordHash: string;
  @Column({ name: 'is_owner', default: false }) isOwner: boolean;
  @Column({ default: 'active' }) status: string;
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
}

// ============================================================
// DTOs
// ============================================================
export class RegisterDto {
  @ApiProperty({ example: 'Agência XYZ' })
  @IsString() @MinLength(2) tenantName: string;

  @ApiProperty({ example: '12345678000195', description: 'CNPJ sem máscara' })
  @IsString() @MinLength(14) tenantDocument: string;

  @ApiProperty({ example: 'contato@agencia.com' })
  @IsEmail() tenantEmail: string;

  @ApiProperty({ example: 'João Silva' })
  @IsString() @MinLength(2) userName: string;

  @ApiProperty({ example: 'admin@agencia.com' })
  @IsEmail() userEmail: string;

  @ApiProperty({ example: 'senhaSegura123', minLength: 6 })
  @IsString() @MinLength(6) password: string;
}

export class LoginDto {
  @ApiProperty({ example: 'admin@agencia.com' })
  @IsEmail() email: string;

  @ApiProperty({ example: 'senhaSegura123' })
  @IsString() password: string;
}

export class CreateCompanyDto {
  @ApiProperty({ example: 'Nova Empresa Ltda' })
  @IsString() @MinLength(2) name: string;

  @ApiProperty({ example: '98765432000100', description: 'CNPJ sem máscara' })
  @IsString() @MinLength(14) document: string;

  @ApiProperty({ example: 'nova@empresa.com' })
  @IsEmail() email: string;
}

export class SwitchTenantDto {
  @ApiProperty({ description: 'ID do tenant para o qual deseja trocar' })
  @IsString() tenantId: string;
}

export class ChangePasswordDto {
  @ApiProperty() @IsString() currentPassword: string;
  @ApiProperty({ minLength: 8 }) @IsString() @MinLength(8) newPassword: string;
}

// ============================================================
// JWT STRATEGY
// ============================================================
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get<string>('JWT_SECRET'),
    });
  }

  async validate(payload: { sub: string; tenantId: string; email: string; role: string }) {
    return {
      userId: payload.sub,
      tenantId: payload.tenantId,
      email: payload.email,
      role: payload.role,
    };
  }
}

export const JwtAuthGuard = AuthGuard('jwt');

// ============================================================
// SERVICE
// ============================================================
@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,

    @InjectRepository(Tenant)
    private readonly tenantRepo: Repository<Tenant>,

    @InjectRepository(UserTenantMembership)
    private readonly membershipRepo: Repository<UserTenantMembership>,

    private readonly jwtService: JwtService,
  ) {}

  async register(dto: RegisterDto) {
    const exists = await this.tenantRepo.findOneBy({ document: dto.tenantDocument });
    if (exists) throw new ConflictException('CNPJ já cadastrado');

    const tenant = await this.tenantRepo.save(
      this.tenantRepo.create({
        name: dto.tenantName,
        document: dto.tenantDocument,
        email: dto.tenantEmail,
      }),
    );

    const passwordHash = await bcrypt.hash(dto.password, 10);
    const user = await this.userRepo.save(
      this.userRepo.create({
        tenantId: tenant.id,
        name: dto.userName,
        email: dto.userEmail,
        passwordHash,
        isOwner: true,
      }),
    );

    await this.membershipRepo.save(
      this.membershipRepo.create({ email: user.email, tenantId: tenant.id, role: 'owner' }),
    );

    return this.generateTokens(user, tenant.id);
  }

  async login(dto: LoginDto) {
    const user = await this.userRepo.findOneBy({ email: dto.email });
    if (!user) throw new UnauthorizedException('Credenciais inválidas');

    const valid = await bcrypt.compare(dto.password, user.passwordHash);
    if (!valid) throw new UnauthorizedException('Credenciais inválidas');

    if (user.status !== 'active') throw new UnauthorizedException('Usuário inativo');

    // Fetch all tenants accessible by this user
    const memberships = await this.membershipRepo.find({
      where: { email: user.email, status: 'active' },
    });
    const tenantIds = memberships.map((m) => m.tenantId);
    const tenants = tenantIds.length > 0
      ? await this.tenantRepo.findByIds(tenantIds)
      : [await this.tenantRepo.findOneBy({ id: user.tenantId })];

    const tokens = this.generateTokens(user, user.tenantId);
    return {
      ...tokens,
      tenants: tenants.filter(Boolean).map((t) => ({
        id: t.id,
        name: t.name,
        document: t.document,
        plan: t.plan,
      })),
    };
  }

  async myTenants(email: string) {
    const memberships = await this.membershipRepo.find({
      where: { email, status: 'active' },
    });
    if (!memberships.length) return [];
    const tenantIds = memberships.map((m) => m.tenantId);
    const tenants = await this.tenantRepo.findByIds(tenantIds);
    return tenants.map((t) => ({
      id: t.id,
      name: t.name,
      document: t.document,
      plan: t.plan,
      role: memberships.find((m) => m.tenantId === t.id)?.role ?? 'member',
    }));
  }

  async switchTenant(userId: string, email: string, tenantId: string) {
    const membership = await this.membershipRepo.findOne({
      where: { email, tenantId, status: 'active' },
    });
    if (!membership) throw new UnauthorizedException('Acesso negado a esta empresa');

    const user = await this.userRepo.findOneBy({ id: userId });
    if (!user) throw new UnauthorizedException();

    const tenant = await this.tenantRepo.findOneBy({ id: tenantId });
    if (!tenant) throw new UnauthorizedException('Empresa não encontrada');

    return this.generateTokens(
      { ...user, tenantId, isOwner: membership.role === 'owner' },
      tenantId,
    );
  }

  async createCompany(userId: string, email: string, dto: CreateCompanyDto) {
    const exists = await this.tenantRepo.findOneBy({ document: dto.document });
    if (exists) throw new ConflictException('CNPJ já cadastrado');

    const tenant = await this.tenantRepo.save(
      this.tenantRepo.create({ name: dto.name, document: dto.document, email: dto.email }),
    );

    // Get the registering user's password hash to create a user in the new tenant
    const sourceUser = await this.userRepo.findOneBy({ id: userId });
    const newUser = await this.userRepo.save(
      this.userRepo.create({
        tenantId: tenant.id,
        name: sourceUser.name,
        email: sourceUser.email,
        passwordHash: sourceUser.passwordHash,
        isOwner: true,
      }),
    );

    await this.membershipRepo.save(
      this.membershipRepo.create({ email, tenantId: tenant.id, role: 'owner' }),
    );

    return this.generateTokens(newUser, tenant.id);
  }

  private generateTokens(user: User, tenantId: string) {
    const role = user.isOwner ? 'owner' : 'member';
    const payload = {
      sub: user.id,
      tenantId,
      email: user.email,
      role,
    };
    return {
      access_token: this.jwtService.sign(payload),
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
        role,
        tenantId,
      },
    };
  }

  async me(userId: string) {
    const user = await this.userRepo.findOneBy({ id: userId });
    if (!user) throw new UnauthorizedException();
    const { passwordHash, ...safe } = user as any;
    return safe;
  }

  async changePassword(userId: string, dto: ChangePasswordDto) {
    const user = await this.userRepo.findOneBy({ id: userId });
    if (!user) throw new UnauthorizedException();
    const valid = await bcrypt.compare(dto.currentPassword, user.passwordHash);
    if (!valid) throw new UnauthorizedException('Senha atual incorreta');
    user.passwordHash = await bcrypt.hash(dto.newPassword, 10);
    await this.userRepo.save(user);
    return { message: 'Senha alterada com sucesso' };
  }
}

// ============================================================
// CONTROLLER
// ============================================================
@ApiTags('Auth')
@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('register')
  @ApiOperation({ summary: 'Cadastrar nova empresa + usuário owner' })
  register(@Body() dto: RegisterDto) {
    return this.authService.register(dto);
  }

  @Post('login')
  @ApiOperation({ summary: 'Login e geração de token JWT' })
  login(@Body() dto: LoginDto) {
    return this.authService.login(dto);
  }

  @Get('me')
  @ApiBearerAuth()
  @UseGuards(AuthGuard('jwt'))
  @ApiOperation({ summary: 'Dados do usuário autenticado' })
  me(@Request() req: any) {
    return this.authService.me(req.user.userId);
  }

  @Get('my-tenants')
  @ApiBearerAuth()
  @UseGuards(AuthGuard('jwt'))
  @ApiOperation({ summary: 'Lista todas as empresas que o usuário pode acessar' })
  myTenants(@Request() req: any) {
    return this.authService.myTenants(req.user.email);
  }

  @Post('switch-tenant')
  @ApiBearerAuth()
  @UseGuards(AuthGuard('jwt'))
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Trocar de empresa — retorna novo JWT com o tenantId selecionado' })
  switchTenant(@Request() req: any, @Body() dto: SwitchTenantDto) {
    return this.authService.switchTenant(req.user.userId, req.user.email, dto.tenantId);
  }

  @Post('create-company')
  @ApiBearerAuth()
  @UseGuards(AuthGuard('jwt'))
  @ApiOperation({ summary: 'Criar nova empresa vinculada à conta do usuário' })
  createCompany(@Request() req: any, @Body() dto: CreateCompanyDto) {
    return this.authService.createCompany(req.user.userId, req.user.email, dto);
  }

  @Patch('change-password')
  @ApiBearerAuth()
  @UseGuards(AuthGuard('jwt'))
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Alterar senha do usuário autenticado' })
  changePassword(@Request() req: any, @Body() dto: ChangePasswordDto) {
    return this.authService.changePassword(req.user.userId, dto);
  }
}

// ============================================================
// MODULE
// ============================================================
@Module({
  imports: [
    TypeOrmModule.forFeature([User, Tenant, UserTenantMembership]),
    PassportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        secret: config.get<string>('JWT_SECRET'),
        signOptions: { expiresIn: config.get('JWT_EXPIRATION', '8h') },
      }),
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService, JwtStrategy, JwtModule],
})
export class AuthModule {}
