#!/usr/bin/env node

import express from 'express';
import { DebrosFramework } from '../../../../src/framework/DebrosFramework';
import { User, UserProfile, Category, Post, Comment } from '../models/BlogModels';
import { BlogValidation, ValidationError } from '../models/BlogValidation';

class BlogAPIServer {
  private app: express.Application;
  private framework: DebrosFramework;
  private nodeId: string;

  constructor() {
    this.app = express();
    this.nodeId = process.env.NODE_ID || 'blog-node';
    this.setupMiddleware();
    this.setupRoutes();
  }

  private setupMiddleware() {
    this.app.use(express.json({ limit: '10mb' }));
    this.app.use(express.urlencoded({ extended: true }));

    // CORS
    this.app.use((req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*');
      res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
      res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      if (req.method === 'OPTIONS') {
        res.sendStatus(200);
      } else {
        next();
      }
    });

    // Logging
    this.app.use((req, res, next) => {
      console.log(`[${this.nodeId}] ${new Date().toISOString()} ${req.method} ${req.path}`);
      next();
    });

    // Error handling
    this.app.use((error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
      console.error(`[${this.nodeId}] Error:`, error);
      
      if (error instanceof ValidationError) {
        return res.status(400).json({
          error: error.message,
          field: error.field,
          nodeId: this.nodeId
        });
      }

      res.status(500).json({
        error: 'Internal server error',
        nodeId: this.nodeId
      });
    });
  }

  private setupRoutes() {
    // Health check
    this.app.get('/health', async (req, res) => {
      try {
        const peers = await this.getConnectedPeerCount();
        res.json({ 
          status: 'healthy', 
          nodeId: this.nodeId,
          peers,
          timestamp: Date.now()
        });
      } catch (error) {
        res.status(500).json({
          status: 'unhealthy',
          nodeId: this.nodeId,
          error: error.message
        });
      }
    });

    // API routes
    this.setupUserRoutes();
    this.setupCategoryRoutes();
    this.setupPostRoutes();
    this.setupCommentRoutes();
    this.setupMetricsRoutes();
  }

  private setupUserRoutes() {
    // Create user
    this.app.post('/api/users', async (req, res, next) => {
      try {
        const sanitizedData = BlogValidation.sanitizeUserInput(req.body);
        BlogValidation.validateUser(sanitizedData);

        const user = await User.create(sanitizedData);
        
        console.log(`[${this.nodeId}] Created user: ${user.username} (${user.id})`);
        res.status(201).json(user.toJSON());
      } catch (error) {
        next(error);
      }
    });

    // Get user by ID
    this.app.get('/api/users/:id', async (req, res, next) => {
      try {
        const user = await User.findById(req.params.id);
        if (!user) {
          return res.status(404).json({ 
            error: 'User not found',
            nodeId: this.nodeId 
          });
        }
        res.json(user.toJSON());
      } catch (error) {
        next(error);
      }
    });

    // Get all users
    this.app.get('/api/users', async (req, res, next) => {
      try {
        const page = parseInt(req.query.page as string) || 1;
        const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
        const search = req.query.search as string;

        let query = User.query();

        if (search) {
          query = query.where('username', 'like', `%${search}%`)
            .orWhere('displayName', 'like', `%${search}%`);
        }

        const users = await query
          .orderBy('createdAt', 'desc')
          .limit(limit)
          .offset((page - 1) * limit)
          .find();

        res.json({
          users: users.map(u => u.toJSON()),
          page,
          limit,
          nodeId: this.nodeId
        });
      } catch (error) {
        next(error);
      }
    });

    // Update user
    this.app.put('/api/users/:id', async (req, res, next) => {
      try {
        const user = await User.findById(req.params.id);
        if (!user) {
          return res.status(404).json({ 
            error: 'User not found',
            nodeId: this.nodeId 
          });
        }

        // Only allow updating certain fields
        const allowedFields = ['displayName', 'avatar', 'roles'];
        const updateData: any = {};
        
        allowedFields.forEach(field => {
          if (req.body[field] !== undefined) {
            updateData[field] = req.body[field];
          }
        });

        Object.assign(user, updateData);
        await user.save();
        
        console.log(`[${this.nodeId}] Updated user: ${user.username}`);
        res.json(user.toJSON());
      } catch (error) {
        next(error);
      }
    });

    // User login (update last login)
    this.app.post('/api/users/:id/login', async (req, res, next) => {
      try {
        const user = await User.findById(req.params.id);
        if (!user) {
          return res.status(404).json({ 
            error: 'User not found',
            nodeId: this.nodeId 
          });
        }

        await user.updateLastLogin();
        res.json({ message: 'Login recorded', lastLoginAt: user.lastLoginAt });
      } catch (error) {
        next(error);
      }
    });
  }

  private setupCategoryRoutes() {
    // Create category
    this.app.post('/api/categories', async (req, res, next) => {
      try {
        const sanitizedData = BlogValidation.sanitizeCategoryInput(req.body);
        BlogValidation.validateCategory(sanitizedData);

        const category = await Category.create(sanitizedData);
        
        console.log(`[${this.nodeId}] Created category: ${category.name} (${category.id})`);
        res.status(201).json(category);
      } catch (error) {
        next(error);
      }
    });

    // Get all categories
    this.app.get('/api/categories', async (req, res, next) => {
      try {
        const categories = await Category.query()
          .where('isActive', true)
          .orderBy('name', 'asc')
          .find();

        res.json({
          categories,
          nodeId: this.nodeId
        });
      } catch (error) {
        next(error);
      }
    });

    // Get category by ID
    this.app.get('/api/categories/:id', async (req, res, next) => {
      try {
        const category = await Category.findById(req.params.id);
        if (!category) {
          return res.status(404).json({ 
            error: 'Category not found',
            nodeId: this.nodeId 
          });
        }
        res.json(category);
      } catch (error) {
        next(error);
      }
    });
  }

  private setupPostRoutes() {
    // Create post
    this.app.post('/api/posts', async (req, res, next) => {
      try {
        const sanitizedData = BlogValidation.sanitizePostInput(req.body);
        BlogValidation.validatePost(sanitizedData);

        const post = await Post.create(sanitizedData);
        
        console.log(`[${this.nodeId}] Created post: ${post.title} (${post.id})`);
        res.status(201).json(post);
      } catch (error) {
        next(error);
      }
    });

    // Get post by ID with relationships
    this.app.get('/api/posts/:id', async (req, res, next) => {
      try {
        const post = await Post.query()
          .where('id', req.params.id)
          .with(['author', 'category', 'comments'])
          .first();
          
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }

        res.json(post);
      } catch (error) {
        next(error);
      }
    });

    // Get all posts with pagination and filters
    this.app.get('/api/posts', async (req, res, next) => {
      try {
        const page = parseInt(req.query.page as string) || 1;
        const limit = Math.min(parseInt(req.query.limit as string) || 10, 50);
        const status = req.query.status as string;
        const authorId = req.query.authorId as string;
        const categoryId = req.query.categoryId as string;
        const tag = req.query.tag as string;

        let query = Post.query().with(['author', 'category']);
        
        if (status) {
          query = query.where('status', status);
        }

        if (authorId) {
          query = query.where('authorId', authorId);
        }

        if (categoryId) {
          query = query.where('categoryId', categoryId);
        }

        if (tag) {
          query = query.where('tags', 'includes', tag);
        }

        const posts = await query
          .orderBy('createdAt', 'desc')
          .limit(limit)
          .offset((page - 1) * limit)
          .find();

        res.json({
          posts,
          page,
          limit,
          nodeId: this.nodeId
        });
      } catch (error) {
        next(error);
      }
    });

    // Update post
    this.app.put('/api/posts/:id', async (req, res, next) => {
      try {
        const post = await Post.findById(req.params.id);
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }

        BlogValidation.validatePostUpdate(req.body);

        Object.assign(post, req.body);
        post.updatedAt = Date.now();
        await post.save();
        
        console.log(`[${this.nodeId}] Updated post: ${post.title}`);
        res.json(post);
      } catch (error) {
        next(error);
      }
    });

    // Publish post
    this.app.post('/api/posts/:id/publish', async (req, res, next) => {
      try {
        const post = await Post.findById(req.params.id);
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }
        
        await post.publish();
        console.log(`[${this.nodeId}] Published post: ${post.title}`);
        res.json(post);
      } catch (error) {
        next(error);
      }
    });

    // Unpublish post
    this.app.post('/api/posts/:id/unpublish', async (req, res, next) => {
      try {
        const post = await Post.findById(req.params.id);
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }
        
        await post.unpublish();
        console.log(`[${this.nodeId}] Unpublished post: ${post.title}`);
        res.json(post);
      } catch (error) {
        next(error);
      }
    });

    // Like post
    this.app.post('/api/posts/:id/like', async (req, res, next) => {
      try {
        const post = await Post.findById(req.params.id);
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }
        
        await post.like();
        res.json({ likeCount: post.likeCount });
      } catch (error) {
        next(error);
      }
    });

    // View post (increment view count)
    this.app.post('/api/posts/:id/view', async (req, res, next) => {
      try {
        const post = await Post.findById(req.params.id);
        if (!post) {
          return res.status(404).json({ 
            error: 'Post not found',
            nodeId: this.nodeId 
          });
        }
        
        await post.incrementViews();
        res.json({ viewCount: post.viewCount });
      } catch (error) {
        next(error);
      }
    });
  }

  private setupCommentRoutes() {
    // Create comment
    this.app.post('/api/comments', async (req, res, next) => {
      try {
        const sanitizedData = BlogValidation.sanitizeCommentInput(req.body);
        BlogValidation.validateComment(sanitizedData);

        const comment = await Comment.create(sanitizedData);
        
        console.log(`[${this.nodeId}] Created comment on post ${comment.postId} by ${comment.authorId}`);
        res.status(201).json(comment);
      } catch (error) {
        next(error);
      }
    });

    // Get comments for a post
    this.app.get('/api/posts/:postId/comments', async (req, res, next) => {
      try {
        const comments = await Comment.query()
          .where('postId', req.params.postId)
          .where('isApproved', true)
          .with(['author'])
          .orderBy('createdAt', 'asc')
          .find();

        res.json({
          comments,
          nodeId: this.nodeId
        });
      } catch (error) {
        next(error);
      }
    });

    // Approve comment
    this.app.post('/api/comments/:id/approve', async (req, res, next) => {
      try {
        const comment = await Comment.findById(req.params.id);
        if (!comment) {
          return res.status(404).json({ 
            error: 'Comment not found',
            nodeId: this.nodeId 
          });
        }
        
        await comment.approve();
        console.log(`[${this.nodeId}] Approved comment ${comment.id}`);
        res.json(comment);
      } catch (error) {
        next(error);
      }
    });

    // Like comment
    this.app.post('/api/comments/:id/like', async (req, res, next) => {
      try {
        const comment = await Comment.findById(req.params.id);
        if (!comment) {
          return res.status(404).json({ 
            error: 'Comment not found',
            nodeId: this.nodeId 
          });
        }
        
        await comment.like();
        res.json({ likeCount: comment.likeCount });
      } catch (error) {
        next(error);
      }
    });
  }

  private setupMetricsRoutes() {
    // Network metrics
    this.app.get('/api/metrics/network', async (req, res, next) => {
      try {
        const peers = await this.getConnectedPeerCount();
        res.json({
          nodeId: this.nodeId,
          peers,
          timestamp: Date.now()
        });
      } catch (error) {
        next(error);
      }
    });

    // Data metrics
    this.app.get('/api/metrics/data', async (req, res, next) => {
      try {
        const [userCount, postCount, commentCount, categoryCount] = await Promise.all([
          User.count(),
          Post.count(),
          Comment.count(),
          Category.count()
        ]);

        res.json({
          nodeId: this.nodeId,
          counts: {
            users: userCount,
            posts: postCount,
            comments: commentCount,
            categories: categoryCount
          },
          timestamp: Date.now()
        });
      } catch (error) {
        next(error);
      }
    });

    // Framework metrics
    this.app.get('/api/metrics/framework', async (req, res, next) => {
      try {
        const metrics = this.framework.getMetrics();
        res.json({
          nodeId: this.nodeId,
          ...metrics,
          timestamp: Date.now()
        });
      } catch (error) {
        next(error);
      }
    });
  }

  private async getConnectedPeerCount(): Promise<number> {
    try {
      if (this.framework) {
        const ipfsService = this.framework.getIPFSService();
        if (ipfsService && ipfsService.getConnectedPeers) {
          const peers = await ipfsService.getConnectedPeers();
          return peers.size;
        }
      }
      return 0;
    } catch (error) {
      console.warn(`[${this.nodeId}] Failed to get peer count:`, error.message);
      return 0;
    }
  }

  async start() {
    try {
      console.log(`[${this.nodeId}] Starting Blog API Server...`);

      // Wait for dependencies
      await this.waitForDependencies();

      // Initialize framework
      await this.initializeFramework();

      // Start HTTP server
      const port = process.env.NODE_PORT || 3000;
      this.app.listen(port, () => {
        console.log(`[${this.nodeId}] Blog API server listening on port ${port}`);
        console.log(`[${this.nodeId}] Health check: http://localhost:${port}/health`);
      });

    } catch (error) {
      console.error(`[${this.nodeId}] Failed to start:`, error);
      process.exit(1);
    }
  }

  private async waitForDependencies(): Promise<void> {
    // In a real deployment, you might wait for database connections, etc.
    console.log(`[${this.nodeId}] Dependencies ready`);
  }

  private async initializeFramework(): Promise<void> {
    // Import services
    const { IPFSService } = await import('../../../../src/framework/services/IPFSService');
    const { OrbitDBService } = await import('../../../../src/framework/services/RealOrbitDBService');
    const { FrameworkIPFSService, FrameworkOrbitDBService } = await import('../../../../src/framework/services/OrbitDBService');

    // Initialize IPFS service
    const ipfsService = new IPFSService({
      swarmKeyFile: process.env.SWARM_KEY_FILE,
      bootstrap: process.env.BOOTSTRAP_PEER ? [`/ip4/${process.env.BOOTSTRAP_PEER}/tcp/4001`] : [],
      ports: {
        swarm: parseInt(process.env.IPFS_PORT) || 4001
      }
    });

    await ipfsService.init();
    console.log(`[${this.nodeId}] IPFS service initialized`);

    // Initialize OrbitDB service
    const orbitDBService = new OrbitDBService(ipfsService);
    await orbitDBService.init();
    console.log(`[${this.nodeId}] OrbitDB service initialized`);

    // Wrap services for framework
    const frameworkIPFS = new FrameworkIPFSService(ipfsService);
    const frameworkOrbitDB = new FrameworkOrbitDBService(orbitDBService);

    // Initialize framework
    this.framework = new DebrosFramework({
      environment: 'test',
      features: {
        autoMigration: true,
        automaticPinning: true,
        pubsub: true,
        queryCache: true,
        relationshipCache: true
      },
      performance: {
        queryTimeout: 10000,
        maxConcurrentOperations: 20,
        batchSize: 50
      }
    });

    await this.framework.initialize(frameworkOrbitDB, frameworkIPFS);
    console.log(`[${this.nodeId}] DebrosFramework initialized successfully`);
  }
}

// Handle graceful shutdown
process.on('SIGTERM', () => {
  console.log('Received SIGTERM, shutting down gracefully...');
  process.exit(0);
});

process.on('SIGINT', () => {
  console.log('Received SIGINT, shutting down gracefully...');
  process.exit(0);
});

// Start the server
const server = new BlogAPIServer();
server.start();