import { NextRequest, NextResponse } from 'next/server'
import { Pool } from 'pg'

/**
 * SDK State Management API (Multi-Project)
 *
 * GET /api/sdk/v1/state?tenantId=xxx&projectId=yyy - Load state for a tenant+project
 * POST /api/sdk/v1/state - Save state for a tenant+project
 * DELETE /api/sdk/v1/state?tenantId=xxx&projectId=yyy - Clear state
 *
 * Features:
 * - Versioning for optimistic locking
 * - Last-Modified headers
 * - ETag support for caching
 * - PostgreSQL persistence (with InMemory fallback)
 * - projectId support for multi-project architecture
 */

// =============================================================================
// TYPES
// =============================================================================

interface StoredState {
  state: unknown
  version: number
  userId?: string
  createdAt: string
  updatedAt: string
}

// =============================================================================
// STORAGE LAYER
// =============================================================================

interface StateStore {
  get(tenantId: string, projectId?: string): Promise<StoredState | null>
  save(tenantId: string, state: unknown, userId?: string, expectedVersion?: number, projectId?: string): Promise<StoredState>
  delete(tenantId: string, projectId?: string): Promise<boolean>
}

class InMemoryStateStore implements StateStore {
  private store: Map<string, StoredState> = new Map()

  private key(tenantId: string, projectId?: string): string {
    return projectId ? `${tenantId}:${projectId}` : tenantId
  }

  async get(tenantId: string, projectId?: string): Promise<StoredState | null> {
    return this.store.get(this.key(tenantId, projectId)) || null
  }

  async save(
    tenantId: string,
    state: unknown,
    userId?: string,
    expectedVersion?: number,
    projectId?: string
  ): Promise<StoredState> {
    const k = this.key(tenantId, projectId)
    const existing = this.store.get(k)

    if (expectedVersion !== undefined && existing && existing.version !== expectedVersion) {
      const error = new Error('Version conflict') as Error & { status: number }
      error.status = 409
      throw error
    }

    const now = new Date().toISOString()
    const newVersion = existing ? existing.version + 1 : 1

    const stored: StoredState = {
      state: {
        ...(state as object),
        lastModified: now,
      },
      version: newVersion,
      userId,
      createdAt: existing?.createdAt || now,
      updatedAt: now,
    }

    this.store.set(k, stored)
    return stored
  }

  async delete(tenantId: string, projectId?: string): Promise<boolean> {
    return this.store.delete(this.key(tenantId, projectId))
  }
}

class PostgreSQLStateStore implements StateStore {
  private pool: Pool

  constructor(connectionString: string) {
    // Strip sslmode from URL — pg driver overrides our ssl config if it's in the URL.
    // We handle SSL ourselves via the ssl option below.
    const cleanUrl = connectionString.replace(/[?&]sslmode=[^&]*/g, '').replace(/\?$/, '')
    const needsSsl = connectionString.includes('sslmode=require') || connectionString.includes('sslmode=verify')
    this.pool = new Pool({
      connectionString: cleanUrl,
      max: 5,
      // Set search_path for compliance schema
      options: '-c search_path=compliance,core,public',
      // Accept self-signed certificates (Hetzner PostgreSQL)
      ssl: needsSsl ? { rejectUnauthorized: false } : false,
    })
  }

  async get(tenantId: string, projectId?: string): Promise<StoredState | null> {
    let result
    if (projectId) {
      result = await this.pool.query(
        'SELECT state, version, user_id, created_at, updated_at FROM sdk_states WHERE tenant_id = $1 AND project_id = $2',
        [tenantId, projectId]
      )
    } else {
      // Backwards compatibility: find the single active project for this tenant
      result = await this.pool.query(
        `SELECT s.state, s.version, s.user_id, s.created_at, s.updated_at
         FROM sdk_states s
         LEFT JOIN compliance_projects p ON s.project_id = p.id
         WHERE s.tenant_id = $1
           AND (p.status = 'active' OR p.id IS NULL)
         ORDER BY s.updated_at DESC
         LIMIT 1`,
        [tenantId]
      )
    }
    if (result.rows.length === 0) return null
    const row = result.rows[0]
    return {
      state: row.state,
      version: row.version,
      userId: row.user_id,
      createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
      updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
    }
  }

  async save(tenantId: string, state: unknown, userId?: string, expectedVersion?: number, projectId?: string): Promise<StoredState> {
    const now = new Date().toISOString()
    const stateWithTimestamp = {
      ...(state as object),
      lastModified: now,
    }

    let result

    if (projectId) {
      // Multi-project: UPSERT on (tenant_id, project_id)
      result = await this.pool.query(`
        INSERT INTO sdk_states (tenant_id, project_id, user_id, state, version, created_at, updated_at)
        VALUES ($1, $5, $2, $3::jsonb, 1, NOW(), NOW())
        ON CONFLICT (tenant_id, project_id) DO UPDATE SET
          state = $3::jsonb,
          user_id = COALESCE($2, sdk_states.user_id),
          version = sdk_states.version + 1,
          updated_at = NOW()
        WHERE ($4::int IS NULL OR sdk_states.version = $4)
        RETURNING version, user_id, created_at, updated_at
      `, [tenantId, userId, JSON.stringify(stateWithTimestamp), expectedVersion ?? null, projectId])
    } else {
      // Backwards compatibility: find the single project for this tenant
      // First try to find an existing project
      const projectResult = await this.pool.query(
        `SELECT id FROM compliance_projects WHERE tenant_id = $1 AND status = 'active' ORDER BY created_at ASC LIMIT 1`,
        [tenantId]
      )

      if (projectResult.rows.length > 0) {
        const foundProjectId = projectResult.rows[0].id
        result = await this.pool.query(`
          INSERT INTO sdk_states (tenant_id, project_id, user_id, state, version, created_at, updated_at)
          VALUES ($1, $5, $2, $3::jsonb, 1, NOW(), NOW())
          ON CONFLICT (tenant_id, project_id) DO UPDATE SET
            state = $3::jsonb,
            user_id = COALESCE($2, sdk_states.user_id),
            version = sdk_states.version + 1,
            updated_at = NOW()
          WHERE ($4::int IS NULL OR sdk_states.version = $4)
          RETURNING version, user_id, created_at, updated_at
        `, [tenantId, userId, JSON.stringify(stateWithTimestamp), expectedVersion ?? null, foundProjectId])
      } else {
        // No project exists — create a default one
        const newProject = await this.pool.query(
          `INSERT INTO compliance_projects (tenant_id, name, customer_type, status)
           VALUES ($1, 'Projekt 1', 'new', 'active')
           RETURNING id`,
          [tenantId]
        )
        const newProjectId = newProject.rows[0].id
        result = await this.pool.query(`
          INSERT INTO sdk_states (tenant_id, project_id, user_id, state, version, created_at, updated_at)
          VALUES ($1, $5, $2, $3::jsonb, 1, NOW(), NOW())
          ON CONFLICT (tenant_id, project_id) DO UPDATE SET
            state = $3::jsonb,
            user_id = COALESCE($2, sdk_states.user_id),
            version = sdk_states.version + 1,
            updated_at = NOW()
          WHERE ($4::int IS NULL OR sdk_states.version = $4)
          RETURNING version, user_id, created_at, updated_at
        `, [tenantId, userId, JSON.stringify(stateWithTimestamp), expectedVersion ?? null, newProjectId])
      }
    }

    if (result.rows.length === 0) {
      const error = new Error('Version conflict') as Error & { status: number }
      error.status = 409
      throw error
    }

    const row = result.rows[0]
    return {
      state: stateWithTimestamp,
      version: row.version,
      userId: row.user_id,
      createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
      updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : row.updated_at,
    }
  }

  async delete(tenantId: string, projectId?: string): Promise<boolean> {
    let result
    if (projectId) {
      result = await this.pool.query(
        'DELETE FROM sdk_states WHERE tenant_id = $1 AND project_id = $2',
        [tenantId, projectId]
      )
    } else {
      result = await this.pool.query(
        'DELETE FROM sdk_states WHERE tenant_id = $1',
        [tenantId]
      )
    }
    return (result.rowCount ?? 0) > 0
  }
}

// =============================================================================
// STORE INITIALIZATION
// =============================================================================

function createStateStore(): StateStore {
  const databaseUrl = process.env.DATABASE_URL
  if (databaseUrl) {
    console.log('[SDK State] Using PostgreSQL state store')
    return new PostgreSQLStateStore(databaseUrl)
  }
  console.log('[SDK State] Using in-memory state store (no DATABASE_URL)')
  return new InMemoryStateStore()
}

const stateStore: StateStore = createStateStore()

// =============================================================================
// HELPER FUNCTIONS
// =============================================================================

function generateETag(version: number, updatedAt: string): string {
  return `"${version}-${Buffer.from(updatedAt).toString('base64').slice(0, 8)}"`
}

// =============================================================================
// HANDLERS
// =============================================================================

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url)
    const tenantId = searchParams.get('tenantId')
    const projectId = searchParams.get('projectId') || undefined

    if (!tenantId) {
      return NextResponse.json(
        { success: false, error: 'tenantId is required' },
        { status: 400 }
      )
    }

    const stored = await stateStore.get(tenantId, projectId)

    if (!stored) {
      return NextResponse.json(
        { success: false, error: 'State not found', tenantId },
        { status: 404 }
      )
    }

    const etag = generateETag(stored.version, stored.updatedAt)

    // Check If-None-Match header for caching
    const ifNoneMatch = request.headers.get('If-None-Match')
    if (ifNoneMatch === etag) {
      return new NextResponse(null, { status: 304 })
    }

    return NextResponse.json(
      {
        success: true,
        data: {
          tenantId,
          projectId: projectId || null,
          state: stored.state,
          version: stored.version,
          lastModified: stored.updatedAt,
        },
      },
      {
        headers: {
          'ETag': etag,
          'Last-Modified': stored.updatedAt,
          'Cache-Control': 'private, no-cache',
        },
      }
    )
  } catch (error) {
    console.error('Failed to load SDK state:', error)
    return NextResponse.json(
      { success: false, error: 'Failed to load state' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { tenantId, state, version, projectId } = body

    if (!tenantId) {
      return NextResponse.json(
        { success: false, error: 'tenantId is required' },
        { status: 400 }
      )
    }

    if (!state) {
      return NextResponse.json(
        { success: false, error: 'state is required' },
        { status: 400 }
      )
    }

    // Check If-Match header for optimistic locking
    const ifMatch = request.headers.get('If-Match')
    const expectedVersion = ifMatch ? parseInt(ifMatch, 10) : version

    const stored = await stateStore.save(tenantId, state, body.userId, expectedVersion, projectId || undefined)

    const etag = generateETag(stored.version, stored.updatedAt)

    return NextResponse.json(
      {
        success: true,
        data: {
          tenantId,
          projectId: projectId || null,
          state: stored.state,
          version: stored.version,
          lastModified: stored.updatedAt,
        },
      },
      {
        headers: {
          'ETag': etag,
          'Last-Modified': stored.updatedAt,
        },
      }
    )
  } catch (error) {
    const err = error as Error & { status?: number }

    // Handle version conflict
    if (err.status === 409 || err.message === 'Version conflict') {
      return NextResponse.json(
        {
          success: false,
          error: 'Version conflict. State was modified by another request.',
          code: 'VERSION_CONFLICT',
        },
        { status: 409 }
      )
    }

    console.error('Failed to save SDK state:', error)
    return NextResponse.json(
      { success: false, error: 'Failed to save state' },
      { status: 500 }
    )
  }
}

export async function DELETE(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url)
    const tenantId = searchParams.get('tenantId')
    const projectId = searchParams.get('projectId') || undefined

    if (!tenantId) {
      return NextResponse.json(
        { success: false, error: 'tenantId is required' },
        { status: 400 }
      )
    }

    const deleted = await stateStore.delete(tenantId, projectId)

    if (!deleted) {
      return NextResponse.json(
        { success: false, error: 'State not found', tenantId },
        { status: 404 }
      )
    }

    return NextResponse.json({
      success: true,
      tenantId,
      deletedAt: new Date().toISOString(),
    })
  } catch (error) {
    console.error('Failed to delete SDK state:', error)
    return NextResponse.json(
      { success: false, error: 'Failed to delete state' },
      { status: 500 }
    )
  }
}

// =============================================================================
// HEALTH CHECK
// =============================================================================

export async function OPTIONS() {
  return NextResponse.json({ status: 'ok' }, {
    headers: {
      'Allow': 'GET, POST, DELETE, OPTIONS',
    },
  })
}
