import BaseService from './BaseService.js';
import Blog from '../models/community/blog.js';
import BlogPost from '../models/community/blog_post.js';
import User from '../models/community/user.js';
import UserParam from '../models/community/user_param.js';
import UserParamType from '../models/type/user_param.js';
import UserParamValue from '../models/type/user_param_value.js';
import { Op } from 'sequelize';
import Folder from '../models/community/folder.js';
import Image from '../models/community/image.js';
import ImageVisibilityType from '../models/type/image_visibility.js';
import FolderImageVisibility from '../models/community/folder_image_visibility.js';
import friendshipService from './friendshipService.js';
import { notifyUser } from '../utils/socket.js';
import { sendBlogShareEmail } from './emailService.js';
import { sequelize } from '../utils/sequelize.js';

export default class BlogService extends BaseService {
  async createBlog(hashedUserId, { title, description, visibility, ageMin, ageMax, genders }) {
    const user = await this.getUserByHashedId(hashedUserId);
  const blog = await Blog.create({
      userId: user.id,
      title,
      description: description || null,
      visibility: visibility || 'public',
      ageMin: ageMin ?? null,
      ageMax: ageMax ?? null,
      genders: Array.isArray(genders) ? genders.join(',') : (genders || null),
    });
  await this.ensureBlogFolder(user.id);
  await this.applyBlogFolderVisibility(user.id, blog.visibility);
  return blog;
  }

  async assertOwner(hashedUserId, blogId) {
    const user = await this.getUserByHashedId(hashedUserId);
    const blog = await Blog.findByPk(blogId);
    if (!blog) {
      const err = new Error('Blog not found');
      err.status = 404;
      throw err;
    }
    if (blog.userId !== user.id) {
      const err = new Error('Access denied');
      err.status = 403;
      throw err;
    }
    return { user, blog };
  }

  async shareBlog(hashedUserId, blogId, { toFriends = false, emails = [] } = {}, url) {
    const { user, blog } = await this.assertOwner(hashedUserId, blogId);

    // Notify friends (accepted)
    let notifiedFriends = 0;
    if (toFriends) {
      try {
        const friends = await friendshipService.getFriendships(hashedUserId, true);
        for (const f of friends) {
          if (f.accepted && f.user?.hashedId) {
            notifiedFriends++;
            notifyUser(f.user.hashedId, 'blogShared', {
              blogId: blogId,
              title: blog.title,
              url,
              by: user.username,
            });
          }
        }
      } catch (e) {
        // Continue even if notifications fail
        console.error('Error notifying friends for blog share:', e);
      }
    }

    // Send emails
    let emailsSent = 0;
    if (Array.isArray(emails) && emails.length) {
      const uniqueValid = Array.from(new Set(
        emails
          .map(e => (typeof e === 'string' ? e.trim() : ''))
          .filter(e => e && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))
      ));
      for (const to of uniqueValid) {
        try {
          await sendBlogShareEmail(to, url, blog.title, user.username);
          emailsSent++;
        } catch (e) {
          console.error('Error sending blog share email to', to, e);
        }
      }
    }

  return { url, notifiedFriends, emailsSent };
  }

  async resolveSlug(slug) {
    // Try username+title match first (legacy), then title-only match.
    const sqlConcat = `
      SELECT b.id
      FROM community.blog b
      JOIN community.user u ON u.id = b.user_id
      WHERE LOWER(
        regexp_replace(u.username, '[^a-zA-Z0-9_-]', '', 'g') ||
        regexp_replace(regexp_replace(b.title, '\\s+', '', 'g'), '[^a-zA-Z0-9_-]', '', 'g')
      ) = LOWER(:slug)
      LIMIT 1
    `;
    const [concatRows] = await sequelize.query(sqlConcat, { replacements: { slug } });
    if (concatRows && concatRows.length) return concatRows[0].id;
    const sqlTitle = `
      SELECT b.id
      FROM community.blog b
      WHERE LOWER(
        regexp_replace(regexp_replace(b.title, '\\s+', '', 'g'), '[^a-zA-Z0-9_-]', '', 'g')
      ) = LOWER(:slug)
      ORDER BY b.updated_at DESC
      LIMIT 1
    `;
    const [titleRows] = await sequelize.query(sqlTitle, { replacements: { slug } });
    if (titleRows && titleRows.length) return titleRows[0].id;
    return null;
  }

  async updateBlog(hashedUserId, blogId, data) {
    const user = await this.getUserByHashedId(hashedUserId);
    const blog = await Blog.findOne({ where: { id: blogId, userId: user.id } });
    if (!blog) throw new Error('Blog not found or access denied');
    const patch = { ...data };
    if (Array.isArray(patch.genders)) patch.genders = patch.genders.join(',');
    const prevVisibility = blog.visibility;
    await blog.update(patch);
    if (patch.visibility && patch.visibility !== prevVisibility) {
      await this.applyBlogFolderVisibility(user.id, blog.visibility);
    }
    return blog;
  }

  async listBlogsForViewer(viewerHashedId = null) {
    // Public blogs are always visible. Logged-in can also see logged_in, plus filter by age/gender if provided (per blog when set).
    let viewer = null, age = null, gender = null;
    if (viewerHashedId) {
      try { viewer = await this.getUserByHashedId(viewerHashedId); } catch { viewer = null; }
      if (viewer) {
        const params = await this.getUserParams(viewer.id, ['birthdate', 'gender']);
        for (const p of params) {
          if (p.paramType.description === 'birthdate') age = this.calculateAge(p.value);
          if (p.paramType.description === 'gender') {
            const gv = await UserParamValue.findOne({ where: { id: p.value } });
            gender = gv ? gv.value : null;
          }
        }
      }
    }
    const where = viewer ? { } : { visibility: 'public' };
    const blogs = await Blog.findAll({ where, include: [{ model: User, as: 'owner', attributes: ['username', 'hashedId'] }] });
    return blogs.filter(b => this.checkBlogAccess(b, !!viewer, age, gender));
  }

  checkBlogAccess(blog, isLoggedIn, age, gender) {
    if (blog.visibility === 'public') return true;
    if (!isLoggedIn) return false;
    // age range check if provided
    if (blog.ageMin != null && (age == null || age < blog.ageMin)) return false;
    if (blog.ageMax != null && (age == null || age > blog.ageMax)) return false;
    // gender check if provided
    if (blog.genders) {
      const allowed = blog.genders.split(',').map(s => s.trim()).filter(Boolean);
      if (allowed.length && (!gender || !allowed.includes(gender))) return false;
    }
    return true;
  }

  async getBlog(blogId, viewerHashedId = null) {
    const blog = await Blog.findByPk(blogId, { include: [{ model: User, as: 'owner', attributes: ['username', 'hashedId'] }] });
    if (!blog) throw new Error('Blog not found');
    let age = null, gender = null, logged = false;
    if (viewerHashedId) {
      try {
        const viewer = await this.getUserByHashedId(viewerHashedId);
        logged = !!viewer;
        if (viewer) {
          const params = await this.getUserParams(viewer.id, ['birthdate', 'gender']);
          for (const p of params) {
            if (p.paramType.description === 'birthdate') age = this.calculateAge(p.value);
            if (p.paramType.description === 'gender') {
              const gv = await UserParamValue.findOne({ where: { id: p.value } });
              gender = gv ? gv.value : null;
            }
          }
        }
      } catch {}
    }
    if (!this.checkBlogAccess(blog, logged, age, gender)) {
      const err = new Error('Access denied');
      err.status = 403;
      throw err;
    }
    return blog;
  }

  async createPost(hashedUserId, blogId, { title, content }) {
    const user = await this.getUserByHashedId(hashedUserId);
    const blog = await Blog.findOne({ where: { id: blogId } });
    if (!blog) throw new Error('Blog not found');
    // Only logged-in users; editor access: any logged-in (as requested)
    return await BlogPost.create({ blogId: blog.id, userId: user.id, title, content });
  }

  async listPosts(blogId, viewerHashedId = null) {
    // Enforce blog visibility before listing
    await this.getBlog(blogId, viewerHashedId);
    // pagination support via query params will be handled in controller; keep a method variant with args
    return await BlogPost.findAll({ where: { blogId }, order: [['created_at', 'desc']] });
  }

  // Utilities for Blog image folder
  async ensureBlogFolder(userId) {
    // find root folder
    let root = await Folder.findOne({ where: { parentId: null, userId } });
    if (!root) {
      // fallback: create minimal root with default everyone visibility
      root = await Folder.create({ name: 'root', parentId: null, userId });
      const everyone = await ImageVisibilityType.findOne({ where: { description: 'everyone' } });
      if (everyone) await FolderImageVisibility.create({ folderId: root.id, visibilityTypeId: everyone.id });
    }
    let blogFolder = await Folder.findOne({ where: { parentId: root.id, userId, name: 'Blog' } });
    if (!blogFolder) {
      blogFolder = await Folder.create({ name: 'Blog', parentId: root.id, userId });
    }
    return blogFolder;
  }

  async applyBlogFolderVisibility(userId, blogVisibility) {
    const blogFolder = await this.ensureBlogFolder(userId);
    const everyone = await ImageVisibilityType.findOne({ where: { description: 'everyone' } });
    if (!everyone) return;
    // Clear existing visibilities for the Blog folder
    await FolderImageVisibility.destroy({ where: { folderId: blogFolder.id } });
    if (blogVisibility === 'public') {
      await FolderImageVisibility.create({ folderId: blogFolder.id, visibilityTypeId: everyone.id });
    }
    // For non-public blogs we leave folder without 'everyone' (private by default)
    return blogFolder;
  }
}
