import { migrate } from "drizzle-orm/node-postgres/migrator";
import path from "node:path";
import type { PoolClient } from "pg";
import { db, pool, isDatabaseAvailable } from "./index";

const MIGRATIONS_FOLDER = path.join(process.cwd(), "drizzle");

let isInitialized = false;
let initPromise: Promise<boolean> | null = null;

// Idempotent fallback for environments where the drizzle migration files are
// not shipped at runtime (e.g. the Docker standalone image) or where a
// database was already created by an older version of this script. It mirrors
// migrations 0000-0004 plus src/lib/schema/db.ts exactly.
const INIT_SQL = `
-- 1. Users Table
CREATE TABLE IF NOT EXISTS users (
  id VARCHAR(64) PRIMARY KEY,
  email VARCHAR(255),
  email_key VARCHAR(255),
  name VARCHAR(160),
  password_hash VARCHAR(255),
  email_verified_at TIMESTAMP,
  is_guest BOOLEAN DEFAULT TRUE NOT NULL,
  plan VARCHAR(32) DEFAULT 'free' NOT NULL,
  stripe_customer_id VARCHAR(128),
  stripe_subscription_id VARCHAR(128),
  cancel_at_period_end BOOLEAN DEFAULT FALSE NOT NULL,
  scan_count NUMERIC DEFAULT '0' NOT NULL,
  scan_period VARCHAR(7),
  expires_at TIMESTAMP,
  company VARCHAR(255),
  use_case VARCHAR(100),
  monthly_volume VARCHAR(50),
  export_format VARCHAR(100),
  main_pain_point VARCHAR(100),
  referral_source VARCHAR(100),
  onboarding_completed_at TIMESTAMP,
  launch_bonus BOOLEAN DEFAULT FALSE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

-- 2. Late-added users columns (idempotent upgrade for pre-existing databases)
ALTER TABLE users ADD COLUMN IF NOT EXISTS email_key VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS name VARCHAR(160);
ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMP;
ALTER TABLE users ADD COLUMN IF NOT EXISTS company VARCHAR(255);
ALTER TABLE users ADD COLUMN IF NOT EXISTS use_case VARCHAR(100);
ALTER TABLE users ADD COLUMN IF NOT EXISTS monthly_volume VARCHAR(50);
ALTER TABLE users ADD COLUMN IF NOT EXISTS export_format VARCHAR(100);
ALTER TABLE users ADD COLUMN IF NOT EXISTS main_pain_point VARCHAR(100);
ALTER TABLE users ADD COLUMN IF NOT EXISTS referral_source VARCHAR(100);
ALTER TABLE users ADD COLUMN IF NOT EXISTS onboarding_completed_at TIMESTAMP;
ALTER TABLE users ADD COLUMN IF NOT EXISTS launch_bonus BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE users ADD COLUMN IF NOT EXISTS scan_period VARCHAR(7);
ALTER TABLE users ADD COLUMN IF NOT EXISTS cancel_at_period_end BOOLEAN DEFAULT FALSE NOT NULL;

CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_guest_created ON users(is_guest, created_at);
CREATE UNIQUE INDEX IF NOT EXISTS uq_users_email_key ON users(email_key);

-- 3. Sessions Table
CREATE TABLE IF NOT EXISTS sessions (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  user_agent VARCHAR(255),
  ip_hash VARCHAR(64),
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);

-- 4. OAuth Accounts Table
CREATE TABLE IF NOT EXISTS oauth_accounts (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  provider VARCHAR(32) NOT NULL,
  provider_account_id VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS uq_oauth_provider_account ON oauth_accounts(provider, provider_account_id);
CREATE INDEX IF NOT EXISTS idx_oauth_accounts_user_id ON oauth_accounts(user_id);

-- 5. Email Verification Tokens Table
CREATE TABLE IF NOT EXISTS email_verification_tokens (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  email VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  consumed_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_email_verification_user_id ON email_verification_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_email_verification_expires_at ON email_verification_tokens(expires_at);

-- 6. Password Reset Tokens Table
CREATE TABLE IF NOT EXISTS password_reset_tokens (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  consumed_at TIMESTAMP,
  requested_ip_hash VARCHAR(64),
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_password_reset_user_id ON password_reset_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_password_reset_expires_at ON password_reset_tokens(expires_at);

-- 7a. Projects Table (folders for organizing receipts, Pro-only)
CREATE TABLE IF NOT EXISTS projects (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  name VARCHAR(120) NOT NULL,
  color VARCHAR(16),
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_projects_user_id ON projects(user_id);
CREATE INDEX IF NOT EXISTS idx_projects_user_created ON projects(user_id, created_at);
CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_user_name ON projects(user_id, name);

-- 7. Receipts Table
CREATE TABLE IF NOT EXISTS receipts (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  image_hash VARCHAR(128) NOT NULL,
  project_id VARCHAR(64) REFERENCES projects(id) ON DELETE SET NULL,
  storage_url TEXT,
  merchant_name VARCHAR(255),
  receipt_date VARCHAR(32),
  receipt_number VARCHAR(128),
  document_type VARCHAR(64) DEFAULT 'KASSENBON',
  category VARCHAR(64) DEFAULT 'Sonstiges',
  currency VARCHAR(8) DEFAULT 'EUR' NOT NULL,
  total_amount NUMERIC(12, 2) NOT NULL,
  net_amount NUMERIC(12, 2),
  tax_7_amount NUMERIC(12, 2),
  tax_19_amount NUMERIC(12, 2),
  tip_amount NUMERIC(12, 2),
  tax_breakdown_json JSONB,
  line_items_json JSONB,
  validation_json JSONB,
  extraction_json JSONB,
  raw_ocr_text TEXT,
  payment_method VARCHAR(64),
  is_math_valid BOOLEAN DEFAULT TRUE NOT NULL,
  needs_review BOOLEAN DEFAULT FALSE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

-- Late-added receipts columns (idempotent upgrade for pre-existing databases)
ALTER TABLE receipts ADD COLUMN IF NOT EXISTS project_id VARCHAR(64) REFERENCES projects(id) ON DELETE SET NULL;
ALTER TABLE receipts ADD COLUMN IF NOT EXISTS extraction_json JSONB;
ALTER TABLE receipts ALTER COLUMN image_hash TYPE VARCHAR(128);

CREATE INDEX IF NOT EXISTS idx_receipts_user_date ON receipts(user_id, receipt_date);
CREATE INDEX IF NOT EXISTS idx_receipts_hash ON receipts(image_hash);
CREATE INDEX IF NOT EXISTS idx_receipts_duplicate_check ON receipts(user_id, merchant_name, total_amount, receipt_date);
CREATE INDEX IF NOT EXISTS idx_receipts_project ON receipts(user_id, project_id);

-- 8. Line Items Table
CREATE TABLE IF NOT EXISTS line_items (
  id VARCHAR(64) PRIMARY KEY,
  receipt_id VARCHAR(64) REFERENCES receipts(id) ON DELETE CASCADE NOT NULL,
  description TEXT NOT NULL,
  quantity NUMERIC(10, 3) DEFAULT '1' NOT NULL,
  price NUMERIC(12, 2) NOT NULL,
  unit_price NUMERIC(12, 2),
  tax_rate NUMERIC(5, 2),
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_line_items_receipt_id ON line_items(receipt_id);

-- 9. Licenses Table
CREATE TABLE IF NOT EXISTS licenses (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
  license_key VARCHAR(128) NOT NULL UNIQUE,
  plan VARCHAR(32) NOT NULL,
  status VARCHAR(32) DEFAULT 'active' NOT NULL,
  stripe_customer_id VARCHAR(128),
  stripe_subscription_id VARCHAR(128),
  stripe_checkout_session_id VARCHAR(128),
  activated_at TIMESTAMP DEFAULT NOW() NOT NULL,
  expires_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_licenses_key ON licenses(license_key);
CREATE INDEX IF NOT EXISTS idx_licenses_user_id ON licenses(user_id);
CREATE INDEX IF NOT EXISTS idx_licenses_sub_id ON licenses(stripe_subscription_id);

-- 10. Guest Sessions Table
CREATE TABLE IF NOT EXISTS guest_sessions (
  id VARCHAR(64) PRIMARY KEY,
  ip_hash VARCHAR(64),
  scan_count NUMERIC DEFAULT '0' NOT NULL,
  last_scan_at TIMESTAMP,
  expires_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_guest_sessions_ip ON guest_sessions(ip_hash);

-- 11. Waitlist Table
CREATE TABLE IF NOT EXISTS waitlist (
  id VARCHAR(64) PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  name VARCHAR(160),
  source VARCHAR(100) DEFAULT 'landing_page',
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS uq_waitlist_email ON waitlist(email);
CREATE INDEX IF NOT EXISTS idx_waitlist_created_at ON waitlist(created_at);

-- 12. Launch Special Claims (first 500 registrations get 100 bonus scans)
CREATE SEQUENCE IF NOT EXISTS launch_claims_position_seq;
CREATE TABLE IF NOT EXISTS launch_claims (
  id VARCHAR(64) PRIMARY KEY,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE NOT NULL,
  position INTEGER NOT NULL,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE UNIQUE INDEX IF NOT EXISTS uq_launch_claims_user_id ON launch_claims(user_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_launch_claims_position ON launch_claims(position);

-- 13. Security Audit Log (append-only; raw IPs are stored hashed only)
CREATE TABLE IF NOT EXISTS security_events (
  id VARCHAR(64) PRIMARY KEY,
  type VARCHAR(64) NOT NULL,
  user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
  email VARCHAR(255),
  ip_hash VARCHAR(64),
  user_agent VARCHAR(255),
  metadata_json JSONB,
  created_at TIMESTAMP DEFAULT NOW() NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_security_events_type_created ON security_events(type, created_at);
CREATE INDEX IF NOT EXISTS idx_security_events_user_created ON security_events(user_id, created_at);
CREATE INDEX IF NOT EXISTS idx_security_events_ip_created ON security_events(ip_hash, created_at);
`;

/**
 * Idempotent upgrades for databases that already ran an older drizzle
 * migration set. `migrationsAlreadyApplied` otherwise skips INIT_SQL, so
 * these ALTERs would never land.
 */
const RECEIPTS_SCHEMA_UPGRADE_SQL = `
ALTER TABLE receipts ADD COLUMN IF NOT EXISTS extraction_json JSONB;
ALTER TABLE receipts ALTER COLUMN image_hash TYPE VARCHAR(128);
`;

async function applyReceiptsSchemaUpgrade(client: PoolClient): Promise<void> {
  const exists = await client.query(`SELECT to_regclass('public.receipts') AS name`);
  if (!exists.rows[0]?.name) return;
  await client.query(RECEIPTS_SCHEMA_UPGRADE_SQL);
}

/** Same idempotent-upgrade rationale as `applyReceiptsSchemaUpgrade`, for `users`. */
const USERS_SCHEMA_UPGRADE_SQL = `
ALTER TABLE users ADD COLUMN IF NOT EXISTS cancel_at_period_end BOOLEAN DEFAULT FALSE NOT NULL;
`;

async function applyUsersSchemaUpgrade(client: PoolClient): Promise<void> {
  const exists = await client.query(`SELECT to_regclass('public.users') AS name`);
  if (!exists.rows[0]?.name) return;
  await client.query(USERS_SCHEMA_UPGRADE_SQL);
}

/** True when the Drizzle migrator already ran against this database. */
async function migrationsAlreadyApplied(client: PoolClient): Promise<boolean> {
  const table = await client.query(
    `SELECT to_regclass('drizzle.__drizzle_migrations') AS name`
  );
  if (!table.rows[0]?.name) return false;

  const rows = await client.query(
    `SELECT count(*)::int AS count FROM "drizzle"."__drizzle_migrations"`
  );
  return rows.rows[0].count > 0;
}

/**
 * Initializes the PostgreSQL schema. Applies the real Drizzle migrations from
 * the `drizzle/` folder once (tracked in `drizzle.__drizzle_migrations`), and
 * falls back to an idempotent script when the migration files are not shipped
 * at runtime or the database was already created by an older schema init.
 * Safe to call multiple times.
 */
export async function initDatabase(
  force: boolean = false
): Promise<{ success: boolean; error?: string }> {
  if (isInitialized && !force) {
    return { success: true };
  }

  const available = await isDatabaseAvailable();
  if (!available) {
    return {
      success: false,
      error: "PostgreSQL database is currently unreachable or not configured.",
    };
  }

  try {
    const client = await pool.connect();
    try {
      if (await migrationsAlreadyApplied(client)) {
        await applyReceiptsSchemaUpgrade(client);
        await applyUsersSchemaUpgrade(client);
        isInitialized = true;
        return { success: true };
      }

      try {
        await migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
      } catch (migrateError: any) {
        console.warn(
          "Drizzle migrate() not applied (migrations missing at runtime or pre-existing schema); using idempotent schema init.",
          migrateError?.message
        );
        await client.query(INIT_SQL);
      }

      await applyReceiptsSchemaUpgrade(client);
      await applyUsersSchemaUpgrade(client);
      isInitialized = true;
      return { success: true };
    } finally {
      client.release();
    }
  } catch (error: any) {
    console.error("Database initialization error:", error);
    return {
      success: false,
      error: error?.message || "Failed to execute database initialization script.",
    };
  }
}

/**
 * Ensures database tables are initialized before executing queries.
 * Returns true if initialized or false if database is offline.
 */
export async function ensureDatabaseInitialized(): Promise<boolean> {
  if (isInitialized) return true;
  if (initPromise) return initPromise;

  initPromise = (async () => {
    const result = await initDatabase();
    return result.success;
  })();

  const success = await initPromise;
  initPromise = null;
  return success;
}