import {Controller, Delete, Get, Middleware, Post} from '@overnightjs/core';
import {DomainSchemas, UtilitySchemas} from '@plunk/shared';
import type {NextFunction, Request, Response} from 'express';

import {redis} from '../database/redis.js';
import {NotFound} from '../exceptions/index.js';
import type {AuthResponse} from '../middleware/auth.js';
import {isAuthenticated} from '../middleware/auth.js';
import {DomainService} from '../services/DomainService.js';
import {Keys} from '../services/keys.js';
import {prisma} from '../database/prisma.js';
import {CatchAsync} from '../utils/asyncHandler.js';

@Controller('domains')
export class Domains {
  /**
   * Get all domains for a project
   */
  @Get('project/:projectId')
  @Middleware([isAuthenticated])
  @CatchAsync
  public async getProjectDomains(req: Request, res: Response, _next: NextFunction) {
    const auth = res.locals.auth as AuthResponse;
    const {projectId} = DomainSchemas.projectId.parse(req.params);

    // Verify user has access to this project
    const membership = await prisma.membership.findFirst({
      where: {
        userId: auth.userId,
        projectId,
      },
    });

    if (!membership) {
      throw new NotFound('Project not found or you do not have access');
    }

    const domains = await DomainService.getProjectDomains(projectId);

    return res.status(200).json(domains);
  }

  /**
   * Add a new domain to a project
   */
  @Post('')
  @Middleware([isAuthenticated])
  @CatchAsync
  public async addDomain(req: Request, res: Response, _next: NextFunction) {
    const auth = res.locals.auth as AuthResponse;
    const {projectId, domain} = DomainSchemas.create.parse(req.body);

    if (!auth.userId) {
      throw new NotFound('User authentication required');
    }

    // Verify user has admin access to this project
    const membership = await prisma.membership.findFirst({
      where: {
        userId: auth.userId,
        projectId,
        role: {
          in: ['ADMIN', 'OWNER'],
        },
      },
    });

    if (!membership) {
      throw new NotFound('Project not found or you do not have permission');
    }

    // Check if domain is already linked to another project
    const ownershipCheck = await DomainService.checkDomainOwnership(domain, auth.userId);

    if (ownershipCheck.exists) {
      // If domain exists and user is a member of that project, allow it
      if (ownershipCheck.isMember) {
        return res.status(400).json({
          error: `This domain is already linked to project "${ownershipCheck.projectName}". You are a member of that project, so you can use the domain from there.`,
        });
      }

      // Domain exists but user is not a member - deny access
      return res.status(403).json({
        error: 'This domain is already linked to another project. Only members of that project can use this domain.',
      });
    }

    try {
      const newDomain = await DomainService.addDomain(projectId, domain);

      await redis.del(Keys.Domain.project(projectId));

      return res.status(201).json(newDomain);
    } catch (error) {
      if (error instanceof Error) {
        return res.status(400).json({error: error.message});
      }
      throw error;
    }
  }

  /**
   * Check verification status for a domain
   */
  @Get(':id/verify')
  @Middleware([isAuthenticated])
  @CatchAsync
  public async checkVerification(req: Request, res: Response, _next: NextFunction) {
    const auth = res.locals.auth as AuthResponse;
    const {id} = UtilitySchemas.id.parse(req.params);

    const domain = await DomainService.id(id);

    if (!domain) {
      throw new NotFound('Domain not found');
    }

    // Verify user has access to the project this domain belongs to
    const membership = await prisma.membership.findFirst({
      where: {
        userId: auth.userId,
        projectId: domain.projectId,
      },
    });

    if (!membership) {
      throw new NotFound('Domain not found or you do not have access');
    }

    const verificationStatus = await DomainService.checkVerification(id);

    // Invalidate cache if status changed
    await redis.del(Keys.Domain.id(id));
    await redis.del(Keys.Domain.project(domain.projectId));

    return res.status(200).json(verificationStatus);
  }

  /**
   * Remove a domain from a project
   */
  @Delete(':id')
  @Middleware([isAuthenticated])
  @CatchAsync
  public async removeDomain(req: Request, res: Response, _next: NextFunction) {
    const auth = res.locals.auth as AuthResponse;
    const {id} = UtilitySchemas.id.parse(req.params);

    const domain = await DomainService.id(id);

    if (!domain) {
      throw new NotFound('Domain not found');
    }

    // Verify user has admin access to the project this domain belongs to
    const membership = await prisma.membership.findFirst({
      where: {
        userId: auth.userId,
        projectId: domain.projectId,
        role: {
          in: ['ADMIN', 'OWNER'],
        },
      },
    });

    if (!membership) {
      throw new NotFound('Domain not found or you do not have permission');
    }

    await DomainService.removeDomain(id);

    await redis.del(Keys.Domain.id(id));
    await redis.del(Keys.Domain.project(domain.projectId));

    return res.status(200).json({success: true});
  }
}
