import db, { ContractDbRow } from "./db.js";
import { Contract, ContractPayload, UpcomingDeadline } from "./types.js";

const columns = [
  "title",
  "paperless_document_id",
  "provider",
  "category",
  "contract_start_date",
  "contract_end_date",
  "termination_notice_days",
  "renewal_period_months",
  "auto_renew",
  "price_cents",
  "currency",
  "notes",
  "tags"
] as const;

type ColumnName = (typeof columns)[number];

type SerializedPayload = {
  title: string;
  paperless_document_id: number | null;
  provider: string | null;
  category: string | null;
  contract_start_date: string | null;
  contract_end_date: string | null;
  termination_notice_days: number | null;
  renewal_period_months: number | null;
  auto_renew: number;
  price_cents: number | null;
  currency: string;
  notes: string | null;
  tags: string;
};

function toCents(price: number | null | undefined): number | null {
  if (price === null || price === undefined || Number.isNaN(price)) {
    return null;
  }
  return Math.round(price * 100);
}

function fromCents(priceCents: number | null): number | null {
  if (priceCents === null || priceCents === undefined) {
    return null;
  }
  return priceCents / 100;
}

function serializeTags(tags: string[] | undefined): string {
  if (!tags || tags.length === 0) {
    return "[]";
  }
  return JSON.stringify(tags);
}

function parseTags(raw: string | null): string[] {
  if (!raw) {
    return [];
  }
  try {
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function mapRow(row: ContractDbRow): Contract {
  return {
    id: row.id,
    title: row.title,
    paperlessDocumentId: row.paperless_document_id ?? undefined,
    provider: row.provider ?? undefined,
    category: row.category ?? undefined,
    contractStartDate: row.contract_start_date ?? undefined,
    contractEndDate: row.contract_end_date ?? undefined,
    terminationNoticeDays: row.termination_notice_days ?? undefined,
    renewalPeriodMonths: row.renewal_period_months ?? undefined,
    autoRenew: Boolean(row.auto_renew),
    price: fromCents(row.price_cents),
    currency: row.currency ?? undefined,
    notes: row.notes ?? undefined,
    tags: parseTags(row.tags),
    createdAt: row.created_at,
    updatedAt: row.updated_at
  };
}

function serializePayload(payload: ContractPayload): SerializedPayload {
  return {
    title: payload.title,
    paperless_document_id: payload.paperlessDocumentId ?? null,
    provider: payload.provider ?? null,
    category: payload.category ?? null,
    contract_start_date: payload.contractStartDate ?? null,
    contract_end_date: payload.contractEndDate ?? null,
    termination_notice_days: payload.terminationNoticeDays ?? null,
    renewal_period_months: payload.renewalPeriodMonths ?? null,
    auto_renew: payload.autoRenew ? 1 : 0,
    price_cents: toCents(payload.price),
    currency: payload.currency ?? "EUR",
    notes: payload.notes ?? null,
    tags: serializeTags(payload.tags)
  };
}

export function listContracts(skip = 0, limit = 100): Contract[] {
  const stmt = db.prepare<[number, number], ContractDbRow>(
    `SELECT * FROM contracts ORDER BY created_at DESC LIMIT ? OFFSET ?`
  );
  const rows = stmt.all(limit, skip);
  return rows.map(mapRow);
}

export function getContract(id: number): Contract | null {
  const stmt = db.prepare<[number], ContractDbRow>(
    `SELECT * FROM contracts WHERE id = ?`
  );
  const row = stmt.get(id);
  return row ? mapRow(row) : null;
}

export function createContract(payload: ContractPayload): Contract {
  const now = new Date().toISOString();
  const data = serializePayload(payload);
  const values = columns.map((name) => data[name]);
  const placeholders = columns.map(() => "?").join(", ");
  const insert = db.prepare(
    `INSERT INTO contracts (${columns.join(", ")}, created_at, updated_at)
     VALUES (${placeholders}, ?, ?)`
  );

  const result = insert.run(...values, now, now);
  return getContract(Number(result.lastInsertRowid))!;
}

export function updateContract(id: number, payload: Partial<ContractPayload>): Contract | null {
  const current = getContract(id);
  if (!current) {
    return null;
  }

  if (Object.keys(payload).length === 0) {
    return current;
  }

  const { id: _, createdAt, updatedAt, ...rest } = current;
  const merged = { ...rest, ...payload } as ContractPayload;

  merged.title = merged.title || current.title;
  if (merged.tags === undefined) {
    merged.tags = current.tags ?? [];
  }
  if (merged.currency === undefined) {
    merged.currency = current.currency ?? "EUR";
  }
  if (merged.autoRenew === undefined) {
    merged.autoRenew = current.autoRenew ?? false;
  }

  const data = serializePayload(merged);
  const updates = columns.map((column) => `${column} = ?`).join(", ");
  const params = columns.map((column) => data[column]);

  const now = new Date().toISOString();
  const stmt = db.prepare(
    `UPDATE contracts SET ${updates}, updated_at = ? WHERE id = ?`
  );
  stmt.run(...params, now, id);

  return getContract(id);
}

export function deleteContract(id: number): boolean {
  const stmt = db.prepare(`DELETE FROM contracts WHERE id = ?`);
  const result = stmt.run(id);
  return result.changes > 0;
}

function computeTerminationDeadline(contract: Contract): Date | null {
  if (!contract.contractEndDate || !contract.terminationNoticeDays) {
    return null;
  }
  const end = new Date(`${contract.contractEndDate}T00:00:00Z`);
  if (Number.isNaN(end.getTime())) {
    return null;
  }
  const deadline = new Date(end);
  deadline.setUTCDate(deadline.getUTCDate() - contract.terminationNoticeDays);
  return deadline;
}

export function listUpcomingDeadlines(withinDays: number): UpcomingDeadline[] {
  const stmt = db.prepare<[], ContractDbRow>(`SELECT * FROM contracts`);
  const rows = stmt.all();
  const items = rows.map(mapRow);
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  const horizon = new Date(today);
  horizon.setUTCDate(horizon.getUTCDate() + withinDays);

  const upcoming: UpcomingDeadline[] = [];

  for (const contract of items) {
    const deadline = computeTerminationDeadline(contract);
    if (!deadline) {
      continue;
    }
    if (deadline < today || deadline > horizon) {
      continue;
    }

    const daysLeft = Math.round(
      (deadline.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)
    );

    upcoming.push({
      id: contract.id,
      title: contract.title,
      provider: contract.provider,
      paperlessDocumentId: contract.paperlessDocumentId,
      contractEndDate: contract.contractEndDate,
      terminationDeadline: deadline.toISOString().slice(0, 10),
      daysUntilDeadline: daysLeft
    });
  }

  return upcoming.sort((a, b) => {
    if (!a.terminationDeadline) return 1;
    if (!b.terminationDeadline) return -1;
    return a.terminationDeadline.localeCompare(b.terminationDeadline);
  });
}
