import { Client } from '@notionhq/client'
import type {
  DatabaseObjectResponse,
  PageObjectResponse,
  PartialDatabaseObjectResponse,
  PartialPageObjectResponse,
} from '@notionhq/client/build/src/api-endpoints'

// Type for Notion database schema
export interface NotionDatabaseSchema {
  id: string
  title: string
  properties: NotionProperty[]
}

export interface NotionProperty {
  id: string
  name: string
  type: string
}

// Type for a Notion page/record
export interface NotionRecord {
  id: string
  properties: Record<string, unknown>
}

/**
 * Create a Notion client with the provided API key
 */
export function createNotionClient(apiKey: string): Client {
  return new Client({ auth: apiKey })
}

/**
 * Test connection to Notion API
 */
export async function testNotionConnection(
  apiKey: string
): Promise<{ success: boolean; error?: string }> {
  try {
    const client = createNotionClient(apiKey)
    // Try to get the bot user to verify the API key
    await client.users.me({})
    return { success: true }
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Failed to connect to Notion',
    }
  }
}

/**
 * Get database schema (properties)
 */
export async function getNotionDatabaseSchema(
  apiKey: string,
  databaseId: string
): Promise<NotionDatabaseSchema> {
  const client = createNotionClient(apiKey)

  const database = await client.databases.retrieve({
    database_id: databaseId,
  })

  // Type guard for full database object
  if (!('properties' in database)) {
    throw new Error('Could not retrieve database properties')
  }

  const db = database as DatabaseObjectResponse

  const properties: NotionProperty[] = Object.entries(db.properties).map(
    ([name, prop]) => ({
      id: prop.id,
      name,
      type: prop.type,
    })
  )

  // Get database title
  const titleProp = db.title?.[0]
  const title = titleProp?.type === 'text' ? titleProp.plain_text : databaseId

  return {
    id: db.id,
    title,
    properties,
  }
}

/**
 * Query all records from a Notion database
 */
export async function queryNotionDatabase(
  apiKey: string,
  databaseId: string,
  limit?: number
): Promise<NotionRecord[]> {
  const client = createNotionClient(apiKey)

  const records: NotionRecord[] = []
  let cursor: string | undefined

  do {
    const response = await client.databases.query({
      database_id: databaseId,
      start_cursor: cursor,
      page_size: 100,
    })

    for (const page of response.results) {
      if (isFullPage(page)) {
        records.push({
          id: page.id,
          properties: extractProperties(page.properties),
        })

        if (limit && records.length >= limit) {
          return records.slice(0, limit)
        }
      }
    }

    cursor = response.has_more ? response.next_cursor ?? undefined : undefined
  } while (cursor)

  return records
}

/**
 * Type guard for full page response
 */
function isFullPage(
  page: PageObjectResponse | PartialPageObjectResponse | DatabaseObjectResponse | PartialDatabaseObjectResponse
): page is PageObjectResponse {
  return 'properties' in page && page.object === 'page'
}

/**
 * Extract property values from a Notion page
 */
function extractProperties(
  properties: PageObjectResponse['properties']
): Record<string, unknown> {
  const result: Record<string, unknown> = {}

  for (const [name, prop] of Object.entries(properties)) {
    result[name] = extractPropertyValue(prop)
  }

  return result
}

/**
 * Extract a single property value
 */
function extractPropertyValue(prop: PageObjectResponse['properties'][string]): unknown {
  switch (prop.type) {
    case 'title':
      return prop.title.map((t) => t.plain_text).join('')
    case 'rich_text':
      return prop.rich_text.map((t) => t.plain_text).join('')
    case 'number':
      return prop.number
    case 'select':
      return prop.select?.name ?? null
    case 'multi_select':
      return prop.multi_select.map((s) => s.name)
    case 'status':
      return prop.status?.name ?? null
    case 'date':
      return prop.date?.start ?? null
    case 'checkbox':
      return prop.checkbox
    case 'url':
      return prop.url
    case 'email':
      return prop.email
    case 'phone_number':
      return prop.phone_number
    case 'files':
      return prop.files.map((f) => {
        if (f.type === 'file') {
          return f.file.url
        } else if (f.type === 'external') {
          return f.external.url
        }
        return null
      }).filter(Boolean)
    case 'relation':
      return prop.relation.map((r) => r.id)
    case 'people':
      return prop.people.map((p) => {
        if ('name' in p) {
          return p.name
        }
        return p.id
      })
    case 'created_time':
      return prop.created_time
    case 'last_edited_time':
      return prop.last_edited_time
    case 'created_by':
      return 'name' in prop.created_by ? prop.created_by.name : prop.created_by.id
    case 'last_edited_by':
      return 'name' in prop.last_edited_by ? prop.last_edited_by.name : prop.last_edited_by.id
    case 'formula':
      return extractFormulaValue(prop.formula)
    case 'rollup':
      return extractRollupValue(prop.rollup)
    case 'unique_id':
      return prop.unique_id.prefix
        ? `${prop.unique_id.prefix}-${prop.unique_id.number}`
        : prop.unique_id.number
    default:
      return null
  }
}

/**
 * Extract formula value
 */
function extractFormulaValue(
  formula: { type: 'string'; string: string | null } | { type: 'number'; number: number | null } | { type: 'boolean'; boolean: boolean | null } | { type: 'date'; date: { start: string } | null }
): unknown {
  switch (formula.type) {
    case 'string':
      return formula.string
    case 'number':
      return formula.number
    case 'boolean':
      return formula.boolean
    case 'date':
      return formula.date?.start ?? null
    default:
      return null
  }
}

/**
 * Extract rollup value
 */
function extractRollupValue(
  rollup: { type: 'number'; number: number | null; function: string } | { type: 'date'; date: { start: string } | null; function: string } | { type: 'array'; array: Array<unknown>; function: string } | { type: 'incomplete'; incomplete: Record<string, never>; function: string } | { type: 'unsupported'; unsupported: Record<string, never>; function: string }
): unknown {
  switch (rollup.type) {
    case 'number':
      return rollup.number
    case 'date':
      return rollup.date?.start ?? null
    case 'array':
      return rollup.array
    default:
      return null
  }
}
