import { eq } from 'drizzle-orm';
import { crmOpportunities } from '@/db/schema';
import { auditAction } from '@/features/foundation/audit-log/service';
import { db } from '@/lib/db';
import { AuthError } from '@/lib/auth/session';
import type {
  OpportunityMarkCancelledPayload,
  OpportunityMarkLostPayload,
  OpportunityMarkNoQuotationPayload,
  OpportunityMarkWonPayload,
  OpportunityReopenPayload
} from '../api/types';
import {
  assertOpportunityBelongsToOrganization,
  type OpportunityAccessContext
} from './service';

type OpportunityRow = typeof crmOpportunities.$inferSelect;

function isClosed(opportunity: OpportunityRow) {
  return (opportunity.outcomeStatus ?? 'open') !== 'open';
}

function assertCanClose(opportunity: OpportunityRow) {
  if (opportunity.pipelineStage !== 'opportunity') {
    throw new AuthError('Opportunity must be in opportunity stage', 400);
  }

  if (isClosed(opportunity)) {
    throw new AuthError('Opportunity is already closed', 400);
  }
}

function buildLegacyPipelineStage(outcomeStatus: OpportunityRow['outcomeStatus']) {
  if (outcomeStatus === 'won') return 'closed_won' as const;
  if (outcomeStatus === 'lost') return 'closed_lost' as const;
  return 'opportunity' as const;
}

async function updateClosedOpportunity(
  opportunity: OpportunityRow,
  organizationId: string,
  userId: string,
  payload:
    | { outcomeStatus: 'won'; reason: string | null; action: 'mark_opportunity_won'; values: OpportunityMarkWonPayload }
    | { outcomeStatus: 'lost'; reason: string; action: 'mark_opportunity_lost'; values: OpportunityMarkLostPayload }
    | {
        outcomeStatus: 'cancelled';
        reason: string;
        action: 'mark_opportunity_cancelled';
        values: OpportunityMarkCancelledPayload;
      }
    | {
        outcomeStatus: 'no_quotation';
        reason: string;
        action: 'mark_opportunity_no_quotation';
        values: OpportunityMarkNoQuotationPayload;
      }
) {
  assertCanClose(opportunity);

  const now = new Date();
  const [updated] = await db
    .update(crmOpportunities)
    .set({
      status: 'closed',
      outcomeStatus: payload.outcomeStatus,
      pipelineStage: buildLegacyPipelineStage(payload.outcomeStatus),
      closedAt: now,
      closedWonAt: payload.outcomeStatus === 'won' ? now : null,
      closedLostAt: payload.outcomeStatus === 'lost' ? now : null,
      closedByUserId: userId,
      poNumber: payload.outcomeStatus === 'won' ? payload.values.poNumber.trim() : null,
      poDate:
        payload.outcomeStatus === 'won' && payload.values.poDate
          ? new Date(payload.values.poDate)
          : null,
      poAmount: payload.outcomeStatus === 'won' ? payload.values.poAmount ?? null : null,
      poCurrency: payload.outcomeStatus === 'won' ? payload.values.poCurrency?.trim() || null : null,
      lostReason: payload.outcomeStatus === 'lost' ? payload.values.lostReason : null,
      lostDetail: payload.outcomeStatus === 'lost' ? payload.values.lostDetail?.trim() || null : null,
      lostCompetitor:
        payload.outcomeStatus === 'lost' ? payload.values.lostCompetitor?.trim() || null : null,
      lostRemark: payload.outcomeStatus === 'lost' ? payload.values.lostRemark?.trim() || null : null,
      cancelReason: payload.outcomeStatus === 'cancelled' ? payload.values.cancelReason : null,
      noQuotationReason:
        payload.outcomeStatus === 'no_quotation' ? payload.values.noQuotationReason : null,
      updatedAt: now,
      updatedBy: userId
    })
    .where(eq(crmOpportunities.id, opportunity.id))
    .returning();

  await auditAction({
    organizationId,
    branchId: updated.branchId,
    userId,
    entityType: 'crm_opportunity',
    entityId: updated.id,
    action: payload.action,
    afterData: {
      previousStage: opportunity.status,
      previousOutcomeStatus: opportunity.outcomeStatus ?? 'open',
      newStage: updated.status,
      newOutcomeStatus: updated.outcomeStatus,
      reason: payload.reason,
      actedBy: userId,
      actedAt: now.toISOString(),
      closedAt: updated.closedAt?.toISOString() ?? null
    }
  });

  return updated;
}

export async function markOpportunityWon(
  id: string,
  organizationId: string,
  userId: string,
  payload: OpportunityMarkWonPayload,
  accessContext?: OpportunityAccessContext
) {
  const opportunity = await assertOpportunityBelongsToOrganization(id, organizationId, accessContext);
  if (!payload.poNumber.trim()) {
    throw new AuthError('PO number required', 400);
  }

  return updateClosedOpportunity(opportunity, organizationId, userId, {
    outcomeStatus: 'won',
    action: 'mark_opportunity_won',
    reason: payload.remark?.trim() || null,
    values: payload
  });
}

export async function markOpportunityLost(
  id: string,
  organizationId: string,
  userId: string,
  payload: OpportunityMarkLostPayload,
  accessContext?: OpportunityAccessContext
) {
  const opportunity = await assertOpportunityBelongsToOrganization(id, organizationId, accessContext);
  return updateClosedOpportunity(opportunity, organizationId, userId, {
    outcomeStatus: 'lost',
    action: 'mark_opportunity_lost',
    reason: payload.lostReason,
    values: payload
  });
}

export async function markOpportunityCancelled(
  id: string,
  organizationId: string,
  userId: string,
  payload: OpportunityMarkCancelledPayload,
  accessContext?: OpportunityAccessContext
) {
  const opportunity = await assertOpportunityBelongsToOrganization(id, organizationId, accessContext);
  return updateClosedOpportunity(opportunity, organizationId, userId, {
    outcomeStatus: 'cancelled',
    action: 'mark_opportunity_cancelled',
    reason: payload.cancelReason,
    values: payload
  });
}

export async function markOpportunityNoQuotation(
  id: string,
  organizationId: string,
  userId: string,
  payload: OpportunityMarkNoQuotationPayload,
  accessContext?: OpportunityAccessContext
) {
  const opportunity = await assertOpportunityBelongsToOrganization(id, organizationId, accessContext);
  return updateClosedOpportunity(opportunity, organizationId, userId, {
    outcomeStatus: 'no_quotation',
    action: 'mark_opportunity_no_quotation',
    reason: payload.noQuotationReason,
    values: payload
  });
}

export async function reopenOpportunity(
  id: string,
  organizationId: string,
  userId: string,
  payload: OpportunityReopenPayload,
  accessContext?: OpportunityAccessContext
) {
  const opportunity = await assertOpportunityBelongsToOrganization(id, organizationId, accessContext);

  if (!isClosed(opportunity)) {
    throw new AuthError('Only closed opportunities can be reopened', 400);
  }

  const now = new Date();
  const [updated] = await db
    .update(crmOpportunities)
    .set({
      status: 'quotation_created',
      outcomeStatus: 'open',
      pipelineStage: 'opportunity',
      closedAt: null,
      closedWonAt: null,
      closedLostAt: null,
      closedByUserId: null,
      poNumber: null,
      poDate: null,
      poAmount: null,
      poCurrency: null,
      lostReason: null,
      lostDetail: null,
      lostCompetitor: null,
      lostRemark: null,
      cancelReason: null,
      noQuotationReason: null,
      updatedAt: now,
      updatedBy: userId
    })
    .where(eq(crmOpportunities.id, opportunity.id))
    .returning();

  await auditAction({
    organizationId,
    branchId: updated.branchId,
    userId,
    entityType: 'crm_opportunity',
    entityId: updated.id,
    action: 'reopen_opportunity',
    afterData: {
      previousStage: opportunity.status,
      previousOutcomeStatus: opportunity.outcomeStatus ?? 'open',
      newStage: updated.status,
      newOutcomeStatus: updated.outcomeStatus,
      reason: payload.reopenReason.trim(),
      actedBy: userId,
      actedAt: now.toISOString()
    }
  });

  return updated;
}
