/**
 * Organization Repository for SentryAgent.ai AgentIdP.
 * All SQL queries for the organizations and organization_members tables live here.
 */

import { Pool, QueryResult } from 'pg';
import { ulid } from 'ulid';
import {
  IOrganization,
  ICreateOrgRequest,
  IUpdateOrgRequest,
  IOrgMember,
  IOrgListFilters,
  OrgRole,
  OrgStatus,
  PlanTier,
} from '../types/organization.js';

/** Raw database row for an organization. */
interface OrgRow {
  organization_id: string;
  name: string;
  slug: string;
  plan_tier: string;
  max_agents: number;
  max_tokens_per_month: number;
  status: string;
  created_at: Date;
  updated_at: Date;
}

/** Raw database row for an organization member. */
interface OrgMemberRow {
  member_id: string;
  organization_id: string;
  agent_id: string;
  role: string;
  joined_at: Date;
}

/**
 * Maps a raw organization database row to the IOrganization domain model.
 *
 * @param row - Raw row from the organizations table.
 * @returns Typed IOrganization object.
 */
function rowToOrg(row: OrgRow): IOrganization {
  return {
    organizationId: row.organization_id,
    name: row.name,
    slug: row.slug,
    planTier: row.plan_tier as PlanTier,
    maxAgents: row.max_agents,
    maxTokensPerMonth: row.max_tokens_per_month,
    status: row.status as OrgStatus,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

/**
 * Maps a raw organization member database row to the IOrgMember domain model.
 *
 * @param row - Raw row from the organization_members table.
 * @returns Typed IOrgMember object.
 */
function rowToMember(row: OrgMemberRow): IOrgMember {
  return {
    memberId: row.member_id,
    organizationId: row.organization_id,
    agentId: row.agent_id,
    role: row.role as OrgRole,
    joinedAt: row.joined_at,
  };
}

/**
 * Repository for all organization database operations.
 * Receives a pg.Pool via constructor injection.
 */
export class OrgRepository {
  /**
   * @param pool - The PostgreSQL connection pool.
   */
  constructor(private readonly pool: Pool) {}

  /**
   * Creates a new organization record in the database.
   *
   * @param data - The fields for the new organization.
   * @returns The created organization record.
   */
  async create(data: ICreateOrgRequest): Promise<IOrganization> {
    const organizationId = 'org_' + ulid();
    const planTier = data.planTier ?? 'free';
    const maxAgents = data.maxAgents ?? 100;
    const maxTokensPerMonth = data.maxTokensPerMonth ?? 10000;

    const result: QueryResult<OrgRow> = await this.pool.query(
      `INSERT INTO organizations
         (organization_id, name, slug, plan_tier, max_agents, max_tokens_per_month, status, created_at, updated_at)
       VALUES ($1, $2, $3, $4, $5, $6, 'active', NOW(), NOW())
       RETURNING *`,
      [organizationId, data.name, data.slug, planTier, maxAgents, maxTokensPerMonth],
    );
    return rowToOrg(result.rows[0]);
  }

  /**
   * Returns a paginated list of organizations with optional status filter.
   *
   * @param filters - Pagination and optional status filter.
   * @returns Object containing the organizations list and total count.
   */
  async findAll(filters: IOrgListFilters): Promise<{ orgs: IOrganization[]; total: number }> {
    const conditions: string[] = [];
    const params: unknown[] = [];
    let paramIndex = 1;

    if (filters.status !== undefined) {
      conditions.push(`status = $${paramIndex++}`);
      params.push(filters.status);
    }

    const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';

    const countResult: QueryResult<{ count: string }> = await this.pool.query(
      `SELECT COUNT(*) as count FROM organizations ${whereClause}`,
      params,
    );
    const total = parseInt(countResult.rows[0].count, 10);

    const offset = (filters.page - 1) * filters.limit;
    const dataParams = [...params, filters.limit, offset];

    const dataResult: QueryResult<OrgRow> = await this.pool.query(
      `SELECT * FROM organizations ${whereClause}
       ORDER BY created_at DESC
       LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
      dataParams,
    );

    return {
      orgs: dataResult.rows.map(rowToOrg),
      total,
    };
  }

  /**
   * Finds an organization by its ID.
   *
   * @param organizationId - The organization ID.
   * @returns The organization record, or null if not found.
   */
  async findById(organizationId: string): Promise<IOrganization | null> {
    const result: QueryResult<OrgRow> = await this.pool.query(
      'SELECT * FROM organizations WHERE organization_id = $1',
      [organizationId],
    );
    if (result.rows.length === 0) return null;
    return rowToOrg(result.rows[0]);
  }

  /**
   * Finds an organization by its unique slug.
   *
   * @param slug - The organization slug.
   * @returns The organization record, or null if not found.
   */
  async findBySlug(slug: string): Promise<IOrganization | null> {
    const result: QueryResult<OrgRow> = await this.pool.query(
      'SELECT * FROM organizations WHERE slug = $1',
      [slug],
    );
    if (result.rows.length === 0) return null;
    return rowToOrg(result.rows[0]);
  }

  /**
   * Partially updates an organization record.
   *
   * @param organizationId - The organization ID to update.
   * @param data - The fields to update.
   * @returns The updated organization record, or null if not found.
   */
  async update(organizationId: string, data: IUpdateOrgRequest): Promise<IOrganization | null> {
    const setClauses: string[] = [];
    const params: unknown[] = [];
    let paramIndex = 1;

    if (data.name !== undefined) {
      setClauses.push(`name = $${paramIndex++}`);
      params.push(data.name);
    }
    if (data.planTier !== undefined) {
      setClauses.push(`plan_tier = $${paramIndex++}`);
      params.push(data.planTier);
    }
    if (data.maxAgents !== undefined) {
      setClauses.push(`max_agents = $${paramIndex++}`);
      params.push(data.maxAgents);
    }
    if (data.maxTokensPerMonth !== undefined) {
      setClauses.push(`max_tokens_per_month = $${paramIndex++}`);
      params.push(data.maxTokensPerMonth);
    }
    if (data.status !== undefined) {
      setClauses.push(`status = $${paramIndex++}`);
      params.push(data.status);
    }

    if (setClauses.length === 0) return null;

    setClauses.push(`updated_at = NOW()`);
    params.push(organizationId);

    const result: QueryResult<OrgRow> = await this.pool.query(
      `UPDATE organizations SET ${setClauses.join(', ')}
       WHERE organization_id = $${paramIndex}
       RETURNING *`,
      params,
    );

    if (result.rows.length === 0) return null;
    return rowToOrg(result.rows[0]);
  }

  /**
   * Soft-deletes an organization by setting its status to 'deleted'.
   *
   * @param organizationId - The organization ID to delete.
   * @returns True if the record was found and updated, false otherwise.
   */
  async softDelete(organizationId: string): Promise<boolean> {
    const result = await this.pool.query(
      `UPDATE organizations
       SET status = 'deleted', updated_at = NOW()
       WHERE organization_id = $1`,
      [organizationId],
    );
    return (result.rowCount ?? 0) > 0;
  }

  /**
   * Adds an agent as a member of an organization.
   *
   * @param organizationId - The organization ID.
   * @param agentId - The agent ID to add.
   * @param role - The role to assign to the agent.
   * @returns The created membership record.
   */
  async addMember(organizationId: string, agentId: string, role: OrgRole): Promise<IOrgMember> {
    const memberId = 'mem_' + ulid();
    const result: QueryResult<OrgMemberRow> = await this.pool.query(
      `INSERT INTO organization_members
         (member_id, organization_id, agent_id, role, joined_at)
       VALUES ($1, $2, $3, $4, NOW())
       RETURNING *`,
      [memberId, organizationId, agentId, role],
    );
    return rowToMember(result.rows[0]);
  }

  /**
   * Finds a membership record for a specific agent and organization.
   *
   * @param organizationId - The organization ID.
   * @param agentId - The agent ID.
   * @returns The membership record, or null if not found.
   */
  async findMember(organizationId: string, agentId: string): Promise<IOrgMember | null> {
    const result: QueryResult<OrgMemberRow> = await this.pool.query(
      'SELECT * FROM organization_members WHERE organization_id = $1 AND agent_id = $2',
      [organizationId, agentId],
    );
    if (result.rows.length === 0) return null;
    return rowToMember(result.rows[0]);
  }

  /**
   * Counts the number of active agents belonging to an organization.
   *
   * @param organizationId - The organization ID.
   * @returns The count of active agents.
   */
  async countActiveAgents(organizationId: string): Promise<number> {
    const result: QueryResult<{ count: string }> = await this.pool.query(
      `SELECT COUNT(*) as count FROM agents
       WHERE organization_id = $1 AND status = 'active'`,
      [organizationId],
    );
    return parseInt(result.rows[0].count, 10);
  }
}
