const logger = require('winston');
const returnShortId = require('./utils/returnShortId.js');
const isApprovedChannel = require('../../utils/isApprovedChannel');
const { assetDefaults: { thumbnail: defaultThumbnail }, details: { host } } = require('@config/siteConfig');
const { publishing: { serveOnlyApproved, approvedChannels } } = require('@config/siteConfig');

const NO_CLAIM = 'NO_CLAIM';

function determineFileExtensionFromContentType (contentType) {
  switch (contentType) {
    case 'image/jpeg':
    case 'image/jpg':
      return 'jpg';
    case 'image/png':
      return 'png';
    case 'image/gif':
      return 'gif';
    case 'video/mp4':
      return 'mp4';
    default:
      logger.debug('setting unknown file type as file extension jpg');
      return 'jpg';
  }
}

function determineThumbnail (storedThumbnail, defaultThumbnail) {
  if (storedThumbnail === '') {
    return defaultThumbnail;
  }
  return storedThumbnail;
}

function prepareClaimData (claim) {
  // logger.debug('preparing claim data based on resolved data:', claim);
  claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail);
  claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
  claim['host'] = host;
  return claim;
}

function isLongClaimId (claimId) {
  return (claimId && (claimId.length === 40));
}

function isShortClaimId (claimId) {
  return (claimId && (claimId.length < 40));
}

module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
  const Claim = sequelize.define(
    'Claim',
    {
      address: {
        type   : STRING,
        default: null,
      },
      amount: {
        type   : DECIMAL(19, 8),
        default: null,
      },
      claimId: {
        type   : STRING,
        default: null,
      },
      claimSequence: {
        type   : INTEGER,
        default: null,
      },
      decodedClaim: {
        type   : BOOLEAN,
        default: null,
      },
      depth: {
        type   : INTEGER,
        default: null,
      },
      effectiveAmount: {
        type   : DECIMAL(19, 8),
        default: null,
      },
      hasSignature: {
        type   : BOOLEAN,
        default: null,
      },
      height: {
        type   : INTEGER,
        default: null,
      },
      hex: {
        type   : TEXT('long'),
        default: null,
      },
      name: {
        type   : STRING,
        default: null,
      },
      nout: {
        type   : INTEGER,
        default: null,
      },
      txid: {
        type   : STRING,
        default: null,
      },
      validAtHeight: {
        type   : INTEGER,
        default: null,
      },
      outpoint: {
        type   : STRING,
        default: null,
      },
      claimType: {
        type   : STRING,
        default: null,
      },
      certificateId: {
        type   : STRING,
        default: null,
      },
      author: {
        type   : STRING,
        default: null,
      },
      description: {
        type   : TEXT('long'),
        default: null,
      },
      language: {
        type   : STRING,
        default: null,
      },
      license: {
        type   : STRING,
        default: null,
      },
      licenseUrl: {
        type   : STRING,
        default: null,
      },
      nsfw: {
        type   : BOOLEAN,
        default: null,
      },
      preview: {
        type   : STRING,
        default: null,
      },
      thumbnail: {
        type   : STRING,
        default: null,
      },
      title: {
        type   : STRING,
        default: null,
      },
      metadataVersion: {
        type   : STRING,
        default: null,
      },
      contentType: {
        type   : STRING,
        default: null,
      },
      source: {
        type   : STRING,
        default: null,
      },
      sourceType: {
        type   : STRING,
        default: null,
      },
      sourceVersion: {
        type   : STRING,
        default: null,
      },
      streamVersion: {
        type   : STRING,
        default: null,
      },
      valueVersion: {
        type   : STRING,
        default: null,
      },
      channelName: {
        type     : STRING,
        allowNull: true,
        default  : null,
      },
    },
    {
      freezeTableName: true,
    }
  );

  Claim.associate = db => {
    Claim.belongsTo(db.File, {
      foreignKey: {
        allowNull: true,
      },
    });
  };

  Claim.getShortClaimIdFromLongClaimId = function (claimId, claimName) {
    logger.debug(`Claim.getShortClaimIdFromLongClaimId for ${claimName}#${claimId}`);
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: { name: claimName },
          order: [['height', 'ASC']],
        })
        .then(result => {
          switch (result.length) {
            case 0:
              throw new Error('No claim(s) found with that claim name');
            default:
              resolve(returnShortId(result, claimId));
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  Claim.getAllChannelClaims = function (channelClaimId) {
    logger.debug(`Claim.getAllChannelClaims for ${channelClaimId}`);
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: { certificateId: channelClaimId },
          order: [['height', 'DESC']],
          raw  : true,  // returns an array of only data, not an array of instances
        })
        .then(channelClaimsArray => {
          switch (channelClaimsArray.length) {
            case 0:
              return resolve(null);
            default:
              channelClaimsArray.forEach(claim => {
                claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
                claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail);
                return claim;
              });
              return resolve(channelClaimsArray);
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  Claim.getClaimIdByLongChannelId = function (channelClaimId, claimName) {
    logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`);
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: { name: claimName, certificateId: channelClaimId },
          order: [['id', 'ASC']],
        })
        .then(result => {
          switch (result.length) {
            case 0:
              return reject(NO_CLAIM);
            case 1:
              return resolve(result[0].claimId);
            default:
              logger.warn(`${result.length} records found for "${claimName}" in channel "${channelClaimId}"`);
              return resolve(result[0].claimId);
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  Claim.validateLongClaimId = function (name, claimId) {
    return new Promise((resolve, reject) => {
      this.findOne({
        where: {
          name,
          claimId,
        },
      })
        .then(result => {
          if (!result) {
            return reject(NO_CLAIM);
          }
          resolve(claimId);
        })
        .catch(error => {
          logger.error(error);
          reject(NO_CLAIM);
        });
    });
  };

  Claim.getLongClaimIdFromShortClaimId = function (name, shortId) {
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: {
            name,
            claimId: {
              [sequelize.Op.like]: `${shortId}%`,
            }},
          order: [['height', 'ASC']],
        })
        .then(result => {
          switch (result.length) {
            case 0:
              return reject(NO_CLAIM);
            default:
              return resolve(result[0].claimId);
          }
        })
        .catch(error => {
          logger.error(error);
          reject(NO_CLAIM);
        });
    });
  };

  Claim.getTopFreeClaimIdByClaimName = function (name) {
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: { name },
          order: [['effectiveAmount', 'DESC'], ['height', 'ASC']],
        })
        .then(result => {
          switch (result.length) {
            case 0:
              return reject(NO_CLAIM);
            default:
              return resolve(result[0].dataValues.claimId);
          }
        })
        .catch(error => {
          logger.error(error);
          reject(NO_CLAIM);
        });
    });
  };

  Claim.getLongClaimId = function (claimName, claimId) {
    logger.debug(`getLongClaimId(${claimName}, ${claimId})`);
    if (isLongClaimId(claimId)) {
      return this.validateLongClaimId(claimName, claimId);
    } else if (isShortClaimId(claimId)) {
      return this.getLongClaimIdFromShortClaimId(claimName, claimId);
    } else {
      return this.getTopFreeClaimIdByClaimName(claimName);
    }
  };

  Claim.fetchClaim = function (name, claimId) {
    logger.debug(`Claim.resolveClaim: ${name} ${claimId}`);
    return new Promise((resolve, reject) => {
      this
        .findAll({
          where: { name, claimId },
        })
        .then(claimArray => {
          switch (claimArray.length) {
            case 0:
              return resolve(null);
            case 1:
              return resolve(prepareClaimData(claimArray[0].dataValues));
            default:
              logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`);
              return resolve(prepareClaimData(claimArray[0].dataValues));
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  Claim.resolveClaim = function (name, claimId) {
    return new Promise((resolve, reject) => {
      this
        .fetchClaim(name, claimId)
        .then(claim => {
          logger.info('resolveClaim claims:', claim);
          if (serveOnlyApproved && !isApprovedChannel({ longId: claim.certificateId }, approvedChannels)) {
            throw new Error('This content is unavailable');
          }
          return resolve(claim);
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  Claim.getOutpoint = function (name, claimId) {
    logger.debug(`finding outpoint for ${name}#${claimId}`);
    return this
      .findAll({
        where     : { name, claimId },
        attributes: ['outpoint'],
      })
      .then(result => {
        logger.debug('outpoint result');
        switch (result.length) {
          case 0:
            throw new Error(`no record found for ${name}#${claimId}`);
          case 1:
            return result[0].dataValues.outpoint;
          default:
            logger.warn(`more than one record matches ${name}#${claimId} in db.Claim`);
            return result[0].dataValues.outpoint;
        }
      })
      .catch(error => {
        throw error;
      });
  };

  Claim.getCurrentHeight = function () {
    return new Promise((resolve, reject) => {
      return this
        .max('height')
        .then(result => {
          if (result) {
            return resolve(result);
          }
          return resolve(100000);
        })
        .catch(error => {
          return reject(error);
        });
    });
  };

  return Claim;
};