/**
 * Session Store - Native session management without OpenCode binary
 * 
 * This provides a complete replacement for OpenCode's session management,
 * allowing NomadArch to work in "Binary-Free Mode".
 */

import { readFile, writeFile, mkdir } from "fs/promises"
import { existsSync } from "fs"
import path from "path"
import { ulid } from "ulid"
import { createLogger } from "../logger"

const log = createLogger({ component: "session-store" })

// Types matching OpenCode's schema for compatibility
export interface SessionMessage {
    id: string
    sessionId: string
    role: "user" | "assistant" | "system" | "tool"
    content?: string
    parts?: MessagePart[]
    createdAt: number
    updatedAt: number
    toolCalls?: ToolCall[]
    toolCallId?: string
    status?: "pending" | "streaming" | "completed" | "error"
}

type IncomingSessionMessage = Omit<SessionMessage, "id" | "sessionId" | "createdAt" | "updatedAt"> & {
    id?: string
    createdAt?: number
    updatedAt?: number
}

export interface MessagePart {
    type: "text" | "tool_call" | "tool_result" | "thinking" | "code"
    content?: string
    toolCall?: ToolCall
    toolResult?: ToolResult
}

export interface ToolCall {
    id: string
    type: "function"
    function: {
        name: string
        arguments: string
    }
}

export interface ToolResult {
    toolCallId: string
    content: string
    isError?: boolean
}

export interface Session {
    id: string
    workspaceId: string
    title?: string
    parentId?: string | null
    createdAt: number
    updatedAt: number
    messageIds: string[]
    model?: {
        providerId: string
        modelId: string
    }
    agent?: string
    revert?: {
        messageID: string
        reason?: string
    } | null
}

export interface SessionStore {
    sessions: Record<string, Session>
    messages: Record<string, SessionMessage>
}

/**
 * Native session management for Binary-Free Mode
 */
export class NativeSessionManager {
    private stores = new Map<string, SessionStore>()
    private dataDir: string

    constructor(dataDir: string) {
        this.dataDir = dataDir
    }

    private getStorePath(workspaceId: string): string {
        return path.join(this.dataDir, workspaceId, "sessions.json")
    }

    private async ensureDir(workspaceId: string): Promise<void> {
        const dir = path.join(this.dataDir, workspaceId)
        if (!existsSync(dir)) {
            await mkdir(dir, { recursive: true })
        }
    }

    private async loadStore(workspaceId: string): Promise<SessionStore> {
        if (this.stores.has(workspaceId)) {
            return this.stores.get(workspaceId)!
        }

        const storePath = this.getStorePath(workspaceId)
        let store: SessionStore = { sessions: {}, messages: {} }

        if (existsSync(storePath)) {
            try {
                const data = await readFile(storePath, "utf-8")
                store = JSON.parse(data)
            } catch (error) {
                log.error({ workspaceId, error }, "Failed to load session store")
            }
        }

        this.stores.set(workspaceId, store)
        return store
    }

    private async saveStore(workspaceId: string): Promise<void> {
        const store = this.stores.get(workspaceId)
        if (!store) return

        await this.ensureDir(workspaceId)
        const storePath = this.getStorePath(workspaceId)
        await writeFile(storePath, JSON.stringify(store, null, 2), "utf-8")
    }

    // Session CRUD operations

    async listSessions(workspaceId: string): Promise<Session[]> {
        const store = await this.loadStore(workspaceId)
        return Object.values(store.sessions).sort((a, b) => b.updatedAt - a.updatedAt)
    }

    async getSession(workspaceId: string, sessionId: string): Promise<Session | null> {
        const store = await this.loadStore(workspaceId)
        return store.sessions[sessionId] ?? null
    }

    async createSession(workspaceId: string, options?: {
        title?: string
        parentId?: string
        model?: { providerId: string; modelId: string }
        agent?: string
    }): Promise<Session> {
        const store = await this.loadStore(workspaceId)
        const now = Date.now()

        const session: Session = {
            id: ulid(),
            workspaceId,
            title: options?.title ?? "New Session",
            parentId: options?.parentId ?? null,
            createdAt: now,
            updatedAt: now,
            messageIds: [],
            model: options?.model,
            agent: options?.agent,
        }

        store.sessions[session.id] = session
        await this.saveStore(workspaceId)

        log.info({ workspaceId, sessionId: session.id }, "Created new session")
        return session
    }

    async updateSession(workspaceId: string, sessionId: string, updates: Partial<Session>): Promise<Session | null> {
        const store = await this.loadStore(workspaceId)
        const session = store.sessions[sessionId]
        if (!session) return null

        const updated = {
            ...session,
            ...updates,
            id: session.id, // Prevent ID change
            workspaceId: session.workspaceId, // Prevent workspace change
            updatedAt: Date.now(),
        }

        store.sessions[sessionId] = updated
        await this.saveStore(workspaceId)
        return updated
    }

    async deleteSession(workspaceId: string, sessionId: string): Promise<boolean> {
        const store = await this.loadStore(workspaceId)
        const session = store.sessions[sessionId]
        if (!session) return false

        // Delete all messages in the session
        for (const messageId of session.messageIds) {
            delete store.messages[messageId]
        }

        delete store.sessions[sessionId]
        await this.saveStore(workspaceId)

        log.info({ workspaceId, sessionId }, "Deleted session")
        return true
    }

    async forkSession(workspaceId: string, sessionId: string): Promise<Session> {
        const store = await this.loadStore(workspaceId)
        const original = store.sessions[sessionId]
        if (!original) throw new Error(`Session not found: ${sessionId}`)

        const now = Date.now()
        const forked: Session = {
            ...original,
            id: ulid(),
            title: original.title ? `${original.title} (fork)` : "Forked Session",
            parentId: original.parentId || original.id,
            createdAt: now,
            updatedAt: now,
            messageIds: [...original.messageIds], // Shallow copy of message IDs
        }

        store.sessions[forked.id] = forked
        await this.saveStore(workspaceId)
        return forked
    }

    async revert(workspaceId: string, sessionId: string, messageId?: string): Promise<Session> {
        const store = await this.loadStore(workspaceId)
        const session = store.sessions[sessionId]
        if (!session) throw new Error(`Session not found: ${sessionId}`)

        if (!messageId) {
            // Revert last message
            if (session.messageIds.length > 0) {
                const lastId = session.messageIds.pop()
                if (lastId) delete store.messages[lastId]
            }
        } else {
            // Revert to specific message
            const index = session.messageIds.indexOf(messageId)
            if (index !== -1) {
                const toDelete = session.messageIds.splice(index + 1)
                for (const id of toDelete) {
                    delete store.messages[id]
                }
            }
        }

        session.updatedAt = Date.now()
        await this.saveStore(workspaceId)
        return session
    }

    // Message operations

    async getSessionMessages(workspaceId: string, sessionId: string): Promise<SessionMessage[]> {
        const store = await this.loadStore(workspaceId)
        const session = store.sessions[sessionId]
        if (!session) return []

        return session.messageIds
            .map(id => store.messages[id])
            .filter((msg): msg is SessionMessage => msg !== undefined)
    }

    async addMessage(workspaceId: string, sessionId: string, message: IncomingSessionMessage): Promise<SessionMessage> {
        const store = await this.loadStore(workspaceId)
        const session = store.sessions[sessionId]
        if (!session) throw new Error(`Session not found: ${sessionId}`)

        const now = Date.now()
        const messageId = message.id ?? ulid()
        const createdAt = typeof message.createdAt === "number" ? message.createdAt : now
        const updatedAt = typeof message.updatedAt === "number" ? message.updatedAt : createdAt

        const newMessage: SessionMessage = {
            ...message,
            id: messageId,
            sessionId,
            createdAt,
            updatedAt,
        }

        store.messages[newMessage.id] = newMessage
        if (!session.messageIds.includes(newMessage.id)) {
            session.messageIds.push(newMessage.id)
        }
        session.updatedAt = updatedAt

        await this.saveStore(workspaceId)
        return newMessage
    }

    async updateMessage(workspaceId: string, messageId: string, updates: Partial<SessionMessage>): Promise<SessionMessage | null> {
        const store = await this.loadStore(workspaceId)
        const message = store.messages[messageId]
        if (!message) return null

        const updated = {
            ...message,
            ...updates,
            id: message.id, // Prevent ID change
            sessionId: message.sessionId, // Prevent session change
            updatedAt: Date.now(),
        }

        store.messages[messageId] = updated
        await this.saveStore(workspaceId)
        return updated
    }

    // Utility

    async clearWorkspace(workspaceId: string): Promise<void> {
        this.stores.delete(workspaceId)
        // Optionally delete file
    }

    getActiveSessionCount(workspaceId: string): number {
        const store = this.stores.get(workspaceId)
        return store ? Object.keys(store.sessions).length : 0
    }

    /**
     * Import sessions from SDK mode format - for migration when switching modes
     */
    async importSessions(workspaceId: string, sessions: Array<{
        id: string
        title?: string
        parentId?: string | null
        createdAt?: number
        updatedAt?: number
        model?: { providerId: string; modelId: string }
        agent?: string
        messages?: Array<{
            id: string
            role: "user" | "assistant" | "system" | "tool"
            content?: string
            createdAt?: number
        }>
    }>): Promise<{ imported: number; skipped: number }> {
        const store = await this.loadStore(workspaceId)
        let imported = 0
        let skipped = 0

        for (const sdkSession of sessions) {
            // Skip if session already exists
            if (store.sessions[sdkSession.id]) {
                skipped++
                continue
            }

            const now = Date.now()
            const session: Session = {
                id: sdkSession.id,
                workspaceId,
                title: sdkSession.title || "Imported Session",
                parentId: sdkSession.parentId ?? null,
                createdAt: sdkSession.createdAt || now,
                updatedAt: sdkSession.updatedAt || now,
                messageIds: [],
                model: sdkSession.model,
                agent: sdkSession.agent,
            }

            // Import messages if provided
            if (sdkSession.messages && Array.isArray(sdkSession.messages)) {
                for (const msg of sdkSession.messages) {
                    const message: SessionMessage = {
                        id: msg.id,
                        sessionId: sdkSession.id,
                        role: msg.role,
                        content: msg.content,
                        createdAt: msg.createdAt || now,
                        updatedAt: msg.createdAt || now,
                        status: "completed"
                    }
                    store.messages[msg.id] = message
                    session.messageIds.push(msg.id)
                }
            }

            store.sessions[sdkSession.id] = session
            imported++
        }

        await this.saveStore(workspaceId)
        log.info({ workspaceId, imported, skipped }, "Imported sessions from SDK mode")
        return { imported, skipped }
    }
}

// Singleton instance
let sessionManager: NativeSessionManager | null = null

export function getSessionManager(dataDir?: string): NativeSessionManager {
    if (!sessionManager) {
        if (!dataDir) {
            throw new Error("Session manager not initialized - provide dataDir")
        }
        sessionManager = new NativeSessionManager(dataDir)
    }
    return sessionManager
}

export function initSessionManager(dataDir: string): NativeSessionManager {
    sessionManager = new NativeSessionManager(dataDir)
    return sessionManager
}
