/**
 * The ordinary CRM core: accounts, contacts, activities.
 *
 * Deliberately unremarkable. The compute-specific intelligence lives in
 * `supply.ts`, `demand.ts` and `allocations.ts`; this file exists so that those
 * have something conventional to hang from, and so importing from an existing
 * CRM is a straight mapping.
 */
import {
  boolean,
  index,
  integer,
  jsonb,
  pgTable,
  text,
  timestamp,
  uniqueIndex,
  uuid,
} from 'drizzle-orm/pg-core';
import {
  accountSideEnum,
  activityTypeEnum,
  affiliationKindEnum,
  confidenceGradeEnum,
  customerSegmentEnum,
  recordSourceEnum,
  supplierTypeEnum,
} from './enums';
import { users } from './identity';

export const accounts = pgTable(
  'accounts',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    name: text('name').notNull(),
    /** Primary key for deduplication in practice; more reliable than name. */
    domain: text('domain'),
    website: text('website'),
    description: text('description'),
    logoUrl: text('logo_url'),

    /**
     * Which side of the market this account sits on. `both` is common and
     * correct — a neocloud may sell capacity to us and buy managed training
     * from us in the same quarter.
     */
    side: accountSideEnum('side').notNull().default('demand'),
    supplierType: supplierTypeEnum('supplier_type'),
    customerSegment: customerSegmentEnum('customer_segment'),

    /** Headquarters. Distinct from where their capacity physically sits. */
    country: text('country'),
    region: text('region'),
    /**
     * Legal jurisdiction governing the relationship. Export controls make this
     * a commercial fact rather than an administrative one — some capacity
     * cannot lawfully serve some customers.
     */
    jurisdiction: text('jurisdiction'),

    /**
     * Corporate ownership — and specifically the ULTIMATE parent.
     *
     * US export controls on advanced computing apply a headquarters test that
     * reaches through the corporate tree: an entity may be restricted because
     * of where its ultimate parent sits, **even though the entity itself is
     * located somewhere unrestricted**. So the country on this record is not
     * sufficient to determine whether a sale is lawful, and a CRM that stores
     * only `country` cannot answer the question at all.
     *
     * See compliance.ts, where the determination is made per allocation.
     */
    ultimateParentAccountId: uuid('ultimate_parent_account_id'),
    ultimateParentName: text('ultimate_parent_name'),
    ultimateParentCountry: text('ultimate_parent_country'),
    /** When ownership was last actually verified, rather than assumed. */
    ownershipVerifiedAt: timestamp('ownership_verified_at', { withTimezone: true }),

    linkedinUrl: text('linkedin_url'),
    twitterUrl: text('twitter_url'),
    githubOrg: text('github_org'),

    ownerUserId: uuid('owner_user_id').references(() => users.id, { onDelete: 'set null' }),

    source: recordSourceEnum('source').notNull().default('manual'),
    /** Where a seeded or enriched claim came from. Shown in the UI. */
    sourceUrl: text('source_url'),
    confidence: confidenceGradeEnum('confidence').notNull().default('confirmed'),

    lastActivityAt: timestamp('last_activity_at', { withTimezone: true }),
    archivedAt: timestamp('archived_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    uniqueIndex('accounts_domain_key').on(t.domain),
    index('accounts_side_idx').on(t.side),
    index('accounts_owner_idx').on(t.ownerUserId),
    index('accounts_name_idx').on(t.name),
  ],
);

export const contacts = pgTable(
  'contacts',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    accountId: uuid('account_id').references(() => accounts.id, { onDelete: 'set null' }),

    fullName: text('full_name').notNull(),
    firstName: text('first_name'),
    lastName: text('last_name'),
    title: text('title'),

    /**
     * Nullable and left null far more often than a generic CRM would expect.
     * PIG never infers an address from a name and a domain: a guessed address
     * is both unreliable and, when it reaches a real person, rude.
     */
    email: text('email'),
    phone: text('phone'),

    linkedinUrl: text('linkedin_url'),
    twitterHandle: text('twitter_handle'),
    githubHandle: text('github_handle'),
    websiteUrl: text('website_url'),
    avatarUrl: text('avatar_url'),

    /**
     * How this person relates to the account. Being named in a company's
     * repository or on its papers does not make someone an employee, and a CRM
     * that conflates authorship with employment will embarrass whoever acts on
     * it.
     */
    affiliation: affiliationKindEnum('affiliation').notNull().default('unknown'),
    /** Set when someone has demonstrably moved on. Keeps the record honest. */
    departedAt: timestamp('departed_at', { withTimezone: true }),

    /** Whether this person can decide, influence, or merely inform a deal. */
    isDecisionMaker: boolean('is_decision_maker').notNull().default(false),

    ownerUserId: uuid('owner_user_id').references(() => users.id, { onDelete: 'set null' }),

    source: recordSourceEnum('source').notNull().default('manual'),
    sourceUrl: text('source_url'),
    confidence: confidenceGradeEnum('confidence').notNull().default('confirmed'),
    /** Free-text note on provenance, shown beside low-confidence records. */
    confidenceNote: text('confidence_note'),

    lastActivityAt: timestamp('last_activity_at', { withTimezone: true }),
    archivedAt: timestamp('archived_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    index('contacts_account_idx').on(t.accountId),
    index('contacts_email_idx').on(t.email),
    index('contacts_name_idx').on(t.fullName),
    index('contacts_confidence_idx').on(t.confidence),
  ],
);

/**
 * The activity stream. Polymorphic by nullable foreign key rather than a
 * generic subject table: there are only four possible parents, Postgres can
 * enforce all four, and the query planner handles the partial indexes well.
 */
export const activities = pgTable(
  'activities',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    type: activityTypeEnum('type').notNull(),
    subject: text('subject'),
    body: text('body'),

    accountId: uuid('account_id').references(() => accounts.id, { onDelete: 'cascade' }),
    contactId: uuid('contact_id').references(() => contacts.id, { onDelete: 'cascade' }),
    /** Set for demand-side deals; see demand.ts. */
    demandDealId: uuid('demand_deal_id'),
    /** Set for supply-side engagements; see supply.ts. */
    supplyDealId: uuid('supply_deal_id'),

    /** Null when the actor was Piggy rather than a person. */
    actorUserId: uuid('actor_user_id').references(() => users.id, { onDelete: 'set null' }),
    /** Set when an agent produced this entry, naming which one. */
    actorAgent: text('actor_agent'),

    occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull().defaultNow(),
    /** Structured payload — stage transitions, Slack permalinks, and so on. */
    meta: jsonb('meta').$type<Record<string, unknown>>(),

    /** External identity, for idempotent sync from Slack, Buzz or email. */
    externalId: text('external_id'),
    source: recordSourceEnum('source').notNull().default('manual'),

    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    index('activities_account_idx').on(t.accountId, t.occurredAt),
    index('activities_contact_idx').on(t.contactId, t.occurredAt),
    index('activities_demand_deal_idx').on(t.demandDealId, t.occurredAt),
    index('activities_supply_deal_idx').on(t.supplyDealId, t.occurredAt),
    uniqueIndex('activities_external_id_key').on(t.externalId),
  ],
);

/**
 * Communication channels linked to an account — a Slack channel, a Buzz room.
 * This is what lets a deal alert land where the deal is actually discussed
 * rather than in a generic firehose nobody reads.
 */
export const channelLinks = pgTable(
  'channel_links',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    /** 'slack' | 'buzz' — kept as text so a new platform needs no migration. */
    platform: text('platform').notNull(),
    /** Slack channel ids are unique only inside a workspace. Buzz uses ''. */
    workspaceId: text('workspace_id').notNull().default(''),
    channelId: text('channel_id').notNull(),
    channelName: text('channel_name'),
    accountId: uuid('account_id').references(() => accounts.id, { onDelete: 'cascade' }),
    /** Notify on stage changes, idle-capacity alerts, renewals due. */
    notifyOn: jsonb('notify_on').$type<string[]>().notNull().default([]),
    linkedByUserId: uuid('linked_by_user_id').references(() => users.id, {
      onDelete: 'set null',
    }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    uniqueIndex('channel_links_platform_workspace_channel_key').on(
      t.platform,
      t.workspaceId,
      t.channelId,
    ),
    index('channel_links_account_idx').on(t.accountId),
  ],
);

/** Denormalised counter, kept for cheap list rendering. */
export const accountStats = pgTable('account_stats', {
  accountId: uuid('account_id')
    .primaryKey()
    .references(() => accounts.id, { onDelete: 'cascade' }),
  openDeals: integer('open_deals').notNull().default(0),
  contactCount: integer('contact_count').notNull().default(0),
  refreshedAt: timestamp('refreshed_at', { withTimezone: true }).notNull().defaultNow(),
});

export type Account = typeof accounts.$inferSelect;
export type NewAccount = typeof accounts.$inferInsert;
export type Contact = typeof contacts.$inferSelect;
export type NewContact = typeof contacts.$inferInsert;
export type Activity = typeof activities.$inferSelect;
