import { describe, it, expect, vi, beforeEach } from "vitest";

// ── Hoisted mocks ─────────────────────────────────────────────────────────────
const { mockGetServerSession, mockDbSelect, mockNotFound, mockRedirect } =
  vi.hoisted(() => ({
    mockGetServerSession: vi.fn(),
    mockDbSelect: vi.fn(),
    mockNotFound: vi.fn(() => {
      throw new Error("NEXT_NOT_FOUND");
    }),
    mockRedirect: vi.fn(() => {
      throw new Error("NEXT_REDIRECT");
    }),
  }));

// ── Auth ──────────────────────────────────────────────────────────────────────
vi.mock("next-auth", () => ({ getServerSession: mockGetServerSession }));
vi.mock("@/app/api/auth/[...nextauth]/authOptions", () => ({ AuthOptions: {} }));

// ── Navigation ────────────────────────────────────────────────────────────────
vi.mock("next/navigation", () => ({
  redirect: mockRedirect,
  notFound: mockNotFound,
}));
vi.mock("next/link", () => ({ default: vi.fn(() => null) }));

// ── Drizzle ORM ───────────────────────────────────────────────────────────────
vi.mock("drizzle-orm", () => ({
  eq: vi.fn((a: unknown, b: unknown) => ({ $eq: [a, b] })),
  inArray: vi.fn((col: unknown, vals: unknown) => ({ $inArray: [col, vals] })),
  and: vi.fn((...args: unknown[]) => ({ $and: args })),
  desc: vi.fn((col: unknown) => ({ $desc: col })),
  sql: vi.fn(),
}));
vi.mock("drizzle-orm/pg-core", () => ({
  alias: vi.fn((table: unknown, _name: string) => table),
}));

// ── DB ────────────────────────────────────────────────────────────────────────
vi.mock("@/app/db/db", () => ({
  db: { get select() { return mockDbSelect; } },
}));

// ── Schemas ───────────────────────────────────────────────────────────────────
vi.mock("@/app/db/schema/crm/hubspot_deal_table", () => ({
  hubspotDealData: { dealId: {}, companyId: {}, coordinator: {}, designer: {} },
}));
vi.mock("@/app/db/schema/crm/hubspot_user_table", () => ({
  hubspotUsers: { hubspotOwnerId: {}, firstName: {}, lastName: {} },
}));
vi.mock("@/app/db/schema/uploaded_files", () => ({
  uploadedFiles: { hubsotDealId: {}, fileType: {}, measureName: {}, uprn: {} },
}));
vi.mock("@/app/db/schema/portfolio_organisation", () => ({
  portfolioOrganisation: { portfolioId: {}, organisationId: {} },
}));
vi.mock("@/app/db/schema/organisation", () => ({
  organisation: { id: {}, hubspotCompanyId: {} },
}));
vi.mock("@/app/db/schema/portfolio", () => ({
  portfolioCapabilities: { portfolioId: {}, userId: {}, capability: {} },
  portfolioUsers: { portfolioId: {}, userId: {}, role: {} },
}));
vi.mock("@/app/db/schema/approvals", () => ({
  dealMeasureApprovals: { hubspotDealId: {}, isApproved: {}, measureName: {} },
}));
vi.mock("@/app/db/schema/removal_requests", () => ({
  propertyRemovalRequests: {
    portfolioId: {},
    hubspotDealId: {},
    type: {},
    status: {},
    requestedAt: {},
  },
}));
vi.mock("@/app/db/schema/users", () => ({
  user: { id: {}, email: {} },
}));

// ── Transforms & lib ──────────────────────────────────────────────────────────
vi.mock("../transforms", () => ({
  classifyDeals: vi.fn((deals: unknown[]) =>
    deals.map((d) => ({ ...(d as object), displayStage: "Test Stage" })),
  ),
}));
vi.mock("@/app/lib/measureDocumentRequirements", () => ({
  getRequiredDocs: vi.fn(() => []),
}));

// ── DealPage component ────────────────────────────────────────────────────────
vi.mock("./DealPage", () => ({ default: vi.fn(() => null) }));

// ── Subject under test ────────────────────────────────────────────────────────
import DealDetailPage from "./page";

// ── Chain helpers ─────────────────────────────────────────────────────────────

// Generic chain: .limit(n) resolves to limitResult; awaiting without .limit
// resolves to directResult (thenable).
function makeSelectChain(
  limitResult: unknown[] = [],
  directResult: unknown[] = [],
) {
  const self: Record<string, unknown> = {};
  self.then = (
    resolve: (v: unknown) => unknown,
    reject: (e: unknown) => unknown,
  ) => Promise.resolve(directResult).then(resolve, reject);
  self.from = vi.fn(() => self);
  self.innerJoin = vi.fn(() => self);
  self.leftJoin = vi.fn(() => self);
  self.where = vi.fn(() => self);
  self.orderBy = vi.fn(() => self);
  self.limit = vi.fn(() => Promise.resolve(limitResult));
  return self;
}

// Deal chain: inspects the WHERE argument's JSON to decide what to return.
// Returns dealRow only when the condition includes $inArray, i.e. after fix.
// Before fix, eq() produces $eq and the deal is not found (empty result).
function makeDealChain(dealRow: Record<string, unknown>) {
  let conditionUsesInArray = false;
  const self: Record<string, unknown> = {};
  self.then = (
    resolve: (v: unknown) => unknown,
    reject: (e: unknown) => unknown,
  ) => Promise.resolve([]).then(resolve, reject);
  self.from = vi.fn(() => self);
  self.leftJoin = vi.fn(() => self);
  self.where = vi.fn((condition: unknown) => {
    conditionUsesInArray = JSON.stringify(condition).includes('"$inArray"');
    return self;
  });
  self.orderBy = vi.fn(() => self);
  self.limit = vi.fn(() =>
    Promise.resolve(conditionUsesInArray ? [dealRow] : []),
  );
  return self;
}

// ── Fixtures ──────────────────────────────────────────────────────────────────

const DEAL_COMPANY = "86970043613";
const OTHER_COMPANIES = ["262091354351", "86797211838"];
const DEAL_ID = "489569618109";
const PORTFOLIO_ID = "737";

const mockDealRow = {
  deal: {
    id: "1",
    dealId: DEAL_ID,
    dealname: "Test Property",
    dealstage: "test",
    companyId: DEAL_COMPANY,
    projectCode: null,
    landlordPropertyId: null,
    uprn: null,
    outcome: null,
    outcomeNotes: null,
    majorConditionIssueDescription: null,
    majorConditionIssuePhotos: null,
    majorConditionIssuePhotosS3: null,
    coordinationStatus: null,
    designStatus: null,
    bookingStatus: null,
    pashubLink: null,
    sharepointLink: null,
    dampmouldGrowth: null,
    dampMouldAndRepairComments: null,
    preSap: null,
    mtpCompletionDate: null,
    mtpReModelCompletionDate: null,
    ioeV3CompletionDate: null,
    proposedMeasures: null,
    approvedPackage: null,
    designCompletionDate: null,
    actualMeasuresInstalled: null,
    installer: null,
    installerHandover: null,
    lodgementStatus: null,
    measuresLodgementDate: null,
    lodgementDate: null,
    confirmedSurveyDate: null,
    confirmedSurveyTime: null,
    surveyedDate: null,
    dealType: null,
    eiScore: null,
    eiScorePotential: null,
    epcSapScore: null,
    potentialPostSapScoreDropdown: null,
    epcPrn: null,
    surveyType: null,
    measuresForPibiOrdered: null,
    pibiOrderDate: null,
    pibiCompletedDate: null,
    propertyHaltedDate: null,
    propertyHaltedReason: null,
    technicalApprovedMeasuresForInstall: null,
    domnaSurveyType: null,
    domnaSurveyDate: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  },
  coordinator: null,
  designer: null,
};

function setupDb() {
  mockGetServerSession.mockResolvedValue({
    user: { email: "test@domna.homes" },
  });

  // 1. Org lookup
  //    limitResult  = [wrong company] — what .limit(1) returns (broken path)
  //    directResult = all three companies — what await-without-limit returns (fixed path)
  mockDbSelect.mockImplementationOnce(() =>
    makeSelectChain(
      [{ hubspotCompanyId: OTHER_COMPANIES[0] }],
      [
        { hubspotCompanyId: DEAL_COMPANY },
        { hubspotCompanyId: OTHER_COMPANIES[0] },
        { hubspotCompanyId: OTHER_COMPANIES[1] },
      ],
    ),
  );

  // 2. Deal lookup — returns deal only when inArray appears in the where clause
  mockDbSelect.mockImplementationOnce(() => makeDealChain(mockDealRow));

  // 3. User lookup
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([{ id: 1n }]));

  // 4. Cap rows (no .limit — awaited via thenable)
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([], []));

  // 5. Role row
  mockDbSelect.mockImplementationOnce(() =>
    makeSelectChain([{ role: "read" }]),
  );

  // 6. Approval rows (no .limit)
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([], []));

  // 7. Removal rows (has .orderBy + .limit)
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([]));

  // 8. Phase-1 docs (no .limit)
  mockDbSelect.mockImplementationOnce(() => makeSelectChain([], []));
}

// ── Tests ─────────────────────────────────────────────────────────────────────

describe("DealDetailPage — multi-organisation portfolio", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("renders deal when its company is not the first linked organisation", async () => {
    setupDb();
    await expect(
      DealDetailPage({
        params: Promise.resolve({ slug: PORTFOLIO_ID, dealId: DEAL_ID }),
      }),
    ).resolves.toBeDefined();
    expect(mockNotFound).not.toHaveBeenCalled();
  });
});
