/**
 * Organization Controller for SentryAgent.ai AgentIdP.
 * HTTP handlers for all organization endpoints. No business logic — delegates to OrgService.
 */

import { Request, Response, NextFunction } from 'express';
import { OrgService } from '../services/OrgService.js';
import { ValidationError, AuthorizationError } from '../utils/errors.js';
import {
  ICreateOrgRequest,
  IUpdateOrgRequest,
  IAddMemberRequest,
  IOrgListFilters,
  OrgStatus,
  PlanTier,
  OrgRole,
} from '../types/organization.js';

/** Valid plan tier values for request validation. */
const VALID_PLAN_TIERS: PlanTier[] = ['free', 'pro', 'enterprise'];

/** Valid org status values for query filter validation. */
const VALID_ORG_STATUSES: OrgStatus[] = ['active', 'suspended', 'deleted'];

/** Valid org role values for membership requests. */
const VALID_ORG_ROLES: OrgRole[] = ['member', 'admin'];

/**
 * Controller for the Organization endpoints.
 * Receives OrgService via constructor injection.
 */
export class OrgController {
  /**
   * @param orgService - The organization service.
   */
  constructor(private readonly orgService: OrgService) {}

  /**
   * Handles POST /organizations — creates a new organization.
   *
   * @param req - Express request with ICreateOrgRequest body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  createOrg = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { name, slug, planTier, maxAgents, maxTokensPerMonth } = req.body as Record<string, unknown>;

      if (typeof name !== 'string' || name.trim().length === 0) {
        throw new ValidationError('Request validation failed.', { field: 'name', reason: 'name is required and must be a non-empty string.' });
      }
      if (typeof slug !== 'string' || slug.trim().length === 0) {
        throw new ValidationError('Request validation failed.', { field: 'slug', reason: 'slug is required and must be a non-empty string.' });
      }
      if (!/^[a-z0-9-]+$/.test(slug)) {
        throw new ValidationError('Request validation failed.', { field: 'slug', reason: 'slug must contain only lowercase letters, digits, and hyphens.' });
      }
      if (planTier !== undefined && !VALID_PLAN_TIERS.includes(planTier as PlanTier)) {
        throw new ValidationError('Request validation failed.', { field: 'planTier', reason: `planTier must be one of: ${VALID_PLAN_TIERS.join(', ')}.` });
      }
      if (maxAgents !== undefined && (typeof maxAgents !== 'number' || maxAgents < 1)) {
        throw new ValidationError('Request validation failed.', { field: 'maxAgents', reason: 'maxAgents must be a positive integer.' });
      }
      if (maxTokensPerMonth !== undefined && (typeof maxTokensPerMonth !== 'number' || maxTokensPerMonth < 1)) {
        throw new ValidationError('Request validation failed.', { field: 'maxTokensPerMonth', reason: 'maxTokensPerMonth must be a positive integer.' });
      }

      const data: ICreateOrgRequest = {
        name: (name as string).trim(),
        slug: (slug as string).trim(),
        planTier: planTier as PlanTier | undefined,
        maxAgents: maxAgents as number | undefined,
        maxTokensPerMonth: maxTokensPerMonth as number | undefined,
      };

      const org = await this.orgService.createOrg(data);
      res.status(201).json(org);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles GET /organizations — returns a paginated list of organizations.
   *
   * @param req - Express request with optional query filters.
   * @param res - Express response.
   * @param next - Express next function.
   */
  listOrgs = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const page = req.query['page'] !== undefined ? parseInt(String(req.query['page']), 10) : 1;
      const limit = req.query['limit'] !== undefined ? parseInt(String(req.query['limit']), 10) : 20;
      const statusParam = req.query['status'] as string | undefined;

      if (isNaN(page) || page < 1) {
        throw new ValidationError('Invalid query parameter value.', { field: 'page', reason: 'page must be a positive integer.' });
      }
      if (isNaN(limit) || limit < 1 || limit > 100) {
        throw new ValidationError('Invalid query parameter value.', { field: 'limit', reason: 'limit must be between 1 and 100.' });
      }
      if (statusParam !== undefined && !VALID_ORG_STATUSES.includes(statusParam as OrgStatus)) {
        throw new ValidationError('Invalid query parameter value.', { field: 'status', reason: `status must be one of: ${VALID_ORG_STATUSES.join(', ')}.` });
      }

      const filters: IOrgListFilters = {
        page,
        limit,
        status: statusParam as OrgStatus | undefined,
      };

      const result = await this.orgService.listOrgs(filters);
      res.status(200).json(result);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles GET /organizations/:orgId — retrieves a single organization.
   *
   * @param req - Express request with orgId path param.
   * @param res - Express response.
   * @param next - Express next function.
   */
  getOrg = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { orgId } = req.params;
      const org = await this.orgService.getOrg(orgId);
      res.status(200).json(org);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles PATCH /organizations/:orgId — partially updates an organization.
   *
   * @param req - Express request with orgId path param and IUpdateOrgRequest body.
   * @param res - Express response.
   * @param next - Express next function.
   */
  updateOrg = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { orgId } = req.params;
      const { name, planTier, maxAgents, maxTokensPerMonth, status } = req.body as Record<string, unknown>;

      if (name !== undefined && (typeof name !== 'string' || (name as string).trim().length === 0)) {
        throw new ValidationError('Request validation failed.', { field: 'name', reason: 'name must be a non-empty string.' });
      }
      if (planTier !== undefined && !VALID_PLAN_TIERS.includes(planTier as PlanTier)) {
        throw new ValidationError('Request validation failed.', { field: 'planTier', reason: `planTier must be one of: ${VALID_PLAN_TIERS.join(', ')}.` });
      }
      if (maxAgents !== undefined && (typeof maxAgents !== 'number' || maxAgents < 1)) {
        throw new ValidationError('Request validation failed.', { field: 'maxAgents', reason: 'maxAgents must be a positive integer.' });
      }
      if (maxTokensPerMonth !== undefined && (typeof maxTokensPerMonth !== 'number' || maxTokensPerMonth < 1)) {
        throw new ValidationError('Request validation failed.', { field: 'maxTokensPerMonth', reason: 'maxTokensPerMonth must be a positive integer.' });
      }
      if (status !== undefined && !['active', 'suspended'].includes(status as string)) {
        throw new ValidationError('Request validation failed.', { field: 'status', reason: "status may only be set to 'active' or 'suspended' via update." });
      }

      const data: IUpdateOrgRequest = {
        name: name !== undefined ? (name as string).trim() : undefined,
        planTier: planTier as PlanTier | undefined,
        maxAgents: maxAgents as number | undefined,
        maxTokensPerMonth: maxTokensPerMonth as number | undefined,
        status: status as 'active' | 'suspended' | undefined,
      };

      const updated = await this.orgService.updateOrg(orgId, data);
      res.status(200).json(updated);
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles DELETE /organizations/:orgId — soft-deletes an organization.
   *
   * @param req - Express request with orgId path param.
   * @param res - Express response (204 No Content).
   * @param next - Express next function.
   */
  deleteOrg = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { orgId } = req.params;
      await this.orgService.deleteOrg(orgId);
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  };

  /**
   * Handles POST /organizations/:orgId/members — adds an agent to an organization.
   *
   * @param req - Express request with orgId path param and IAddMemberRequest body.
   * @param res - Express response (201 Created).
   * @param next - Express next function.
   */
  addMember = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      if (!req.user) {
        throw new AuthorizationError();
      }

      const { orgId } = req.params;
      const { agentId, role } = req.body as Record<string, unknown>;

      if (typeof agentId !== 'string' || agentId.trim().length === 0) {
        throw new ValidationError('Request validation failed.', { field: 'agentId', reason: 'agentId is required and must be a non-empty string.' });
      }
      if (typeof role !== 'string' || !VALID_ORG_ROLES.includes(role as OrgRole)) {
        throw new ValidationError('Request validation failed.', { field: 'role', reason: `role must be one of: ${VALID_ORG_ROLES.join(', ')}.` });
      }

      const data: IAddMemberRequest = {
        agentId: agentId.trim(),
        role: role as OrgRole,
      };

      const member = await this.orgService.addMember(orgId, data);
      res.status(201).json(member);
    } catch (err) {
      next(err);
    }
  };
}
